diff options
| author | kx <kx@radix-linux.su> | 2026-10-09 18:00:15 +0300 |
|---|---|---|
| committer | kx <kx@radix-linux.su> | 2026-10-09 18:00:15 +0300 |
| commit | 065da3509019b15dd0e678072e6be629e5330bef (patch) | |
| tree | 69c934c9e786ac048b4fbe8f4c4c5571661521f4 | |
| parent | d130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff) | |
| download | libmcpu-0.4.0.tar.xz | |
Version 0.4.00.4.0
287 files changed, 59214 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fa8a0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ + +# Files generated by ./bootstrap + +aclocal.m4 +m4/*.m4 +config.h.in +configure +Makefile.in +cpu/Makefile.in +man/Makefile.in +man/ru/Makefile.in +sysroot/Makefile.in +tests/Makefile.in +utils/Makefile.in +compile +config.guess +config.sub +depcomp +install-sh +ltmain.sh +missing +test-driver + + +# Autotools/cache/configure state: + +autom4te.cache +config.h +config.log +config.status +stamp-h1 +Makefile +cpu/Makefile +man/Makefile +man/ru/Makefile +sysroot/Makefile +tests/Makefile +utils/Makefile +libtool +libmcpu.pc + + +# Build products: + +.deps +cpu/.deps +cpu/.libs +tests/*.log +tests/*.trs +tests/test-suite.log +utils/.deps +utils/.libs +utils/.dirstamp +z.output + + +# Distribution products: + +mcpu-cpp-*.tar.gz +mcpu-cpp-*.tar.xz + + +# Editor/backup files: + +*~ +*.swp +*.swo + +*.o +*.lo +*.la @@ -0,0 +1,4 @@ + +Authors of libmcpu (in chronological order of initial contribution) + +Andrey V.Kosteltsev Main author diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..12b3fe7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,949 @@ +2026-10-09 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.4.0. + * Bump version for public release. + +2026-10-08 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.3.35. + * Add CPU/system opcodes SETF=0x0010, CLRF=0x0011, + CLRCPUF=0x0012 and CLRMPUF=0x0013. + * Define writable arithmetic/status flags as PSW bits 0..15: SETF/CLRF + accept AF..INXF, modify exactly one selected bit and reject HF/TF/IF. + * Define PSW[7:0] as the CPU flag byte and PSW[15:8] as the MPU flag + byte; CLRCPUF clears only 0x000000ff and CLRMPUF only 0x0000ff00. + * Preserve HF/TF/IF/MODE and every non-selected flag across the new + instructions; keep JSET/JCLR read selectors unchanged. + * Keep existing LibMPU IADC/ISBB and rotate-through-carry semantics; the + new instructions only provide explicit initial flag state. + * Update assembler, decoder/executor and mcpu-objdump opcode/disassembly + support. + * Add regression coverage for flag writes, bulk clears, assembler rejection, + IADC/ISBB carry input and objdump output. + * Update architecture PDF sources/PDF and English/Russian mcpu-as(1) and + libmcpu(7) manual pages. + * Keep MELF, syscall, HLL Runtime ABI, CRT, linker, loader and lifecycle + contracts unchanged. + +2026-10-06 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.3.34. + * Make both PDF reference manuals independent of the LibMCPU package release + number; package history remains in NEWS.md/ChangeLog. + * Replace the obsolete draft HLL/CRT text with the signed HLL Runtime ABI: + int32/int64/PTR64 signatures, normalized fini, mcpu-ld PROGRAM/MODEL + classification, automatic CRT insertion, McpuStartup ownership, MCPU Libc + hooks, PRE_RUNTIME/RUNNING, life status namespace and finalization. + * Add complete reference appendices for mcpu-as directives, every public + OPCODE16 with operands/descriptions, PSW selectors and RCONST selectors. + * Remove stale release-history and in-tree examples references from the PDF + manuals; examples are a separate package. + * Correct syscall documentation to the stable host-independent MCPU errno ABI. + * Keep ISA, MELF ABI, syscall ABI, HLL ABI, CRT and runtime/linker semantics + unchanged from the verified 0.3.33 baseline. + +2026-10-06 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.3.33. + * Make README.md version-independent and move release history responsibility + explicitly to NEWS.md and ChangeLog. + * Remove hard-coded package release numbers from all English and Russian + man-page .TH source fields; installed --version/--dump-version remain the + authoritative release-number interface. + * Expand README.md, libmcpu(7), mcpu-ld(1), mcpu-run(1) and mcpu-as(1) for + the signed HLL Runtime ABI v1, automatic PROGRAM/MODEL CRT selection, + McpuStartup and low-level/HLL lifecycle ownership. + * Document fatal missing-CRT behavior and the canonical + <MCPU-root>/lib/mcpu CRT installation path. + * Add a regression that prevents release-bound LibMCPU versions from + returning to README.md/man sources. + * Keep ISA, MELF ABI, syscall ABI, HLL ABI, CRT and runtime/linker semantics + unchanged from the user-verified 0.3.32 baseline. + +2026-10-06 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.3.32. + * Make successful mcpu-as and mcpu-ld invocations quiet by default and + preserve their existing object summaries behind -v/--verbose. + * Apply mcpu-ld verbose summaries to partial ET_REL, static ABI-v1 + ET_EXEC, dynamic ABI-v3 ET_EXEC and ABI-v3 ET_DYN output. + * Normalize installed utility --help option descriptions with an aligned + " - " separator and aligned continuation lines. + * Indent mcpu-run Stage actions by four spaces. + * Add regression coverage for quiet defaults and both verbose spellings. + * Document -v/--verbose in the English and Russian mcpu-as(1) and + mcpu-ld(1) manual pages. + * Keep ISA, MELF ABI, syscall ABI, HLL Runtime ABI, CRT and runtime + semantics unchanged from 0.3.31. + +2026-10-06 Andrey V. Kosteltsev <support@radix-linux.su> + + * Release 0.3.31. + * Remove the complete examples/ tree from LibMCPU; manual/book examples + are now intended to be distributed as a separate package. + * Move the canonical mcpu-ld.conf source from top-level etc/ to + sysroot/etc/ without changing its installed <MCPU-root>/etc path. + * Remove the obsolete sysroot/usr/src/mcpu/libc-stub and hll-emul + development trees. + * Move the HLL/CRT-only assembler regression inputs into + tests/fixtures/hll so make check remains self-contained. + * Keep ISA, MELF ABI, syscall ABI, HLL Runtime ABI, CRT behavior and + runtime/linker semantics unchanged from the verified 0.3.30 baseline. + +2026-10-05 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.28. + * Implement the signed MCPU HLL Runtime ABI v1 CRT bridge. + * Add automatic PROGRAM/MODEL classification and CRT insertion to mcpu-ld + without new --program/--model options. + * Add crt0.o/crt1.o/crti.o/crtn.o sources and install only the generated + CRT objects under the MCPU target root. + * Add PRE_RUNTIME/RUNNING startup state, McpuStartup validation, int64 argc + bridging, MODEL life-status handling and ordered HLL finalization. + * Make HLL roots, CRT-private symbols and internal libc hooks non-preemptible + in ABI-v3 executables. + * Add never-installed libc-stub and mcpu-cc output emulators under sysroot; + keep the complete existing examples tree unchanged. + * Add end-to-end HLL CRT regression coverage, including ET_DYN ownership + restrictions and pre-runtime startup failure. + +2026-10-05 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.27. + * Add guest-descriptor dup() and dup2() system calls as __NR_dup=0x126 + and __NR_dup2=0x127. + * Keep guest and HOST descriptor namespaces separate; implement both guest + calls over the HOST __cpu_sys_dup() adapter rather than HOST dup2(). + * Preserve borrowed HOST stdio, POSIX oldfd==newfd behavior, exact dup2 + target replacement, and descriptor-flag clearing on real duplication. + * Add tst-dup regression coverage and manual dup-basic/dup2-basic syscall + examples. + +2026-10-05 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.26. + * Move the canonical public MCPU assembler ABI headers to + sysroot/include/sys/asm/. + * Install sys/asm headers only from the canonical sysroot tree. + * Make tst-errno-abi consume public headers from sysroot, eliminating + the regression-test dependency on examples/. + * Keep examples/syscalls/include/sys/asm/ as a standalone snapshot and + add an explicit maintainer sync target from sysroot to examples. + * Copy historical regression assembler inputs into tests/fixtures/ and + remove all automatic-test references to examples/. + +2026-10-05 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.25. + * Replace raw HOST errno syscall results with the stable MCPU errno ABI. + * Add HOST-to-MCPU errno translation, public sys/asm/errno.h and the + LibMPU-compatible sys/asm/mpu-errno.h Math Processor Unit interface. + * Migrate every existing syscall error path and add errno ABI regressions. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.11. + * Added stat/fstat/lstat with fixed 128-byte struct __cpu_stat ABI. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.10. + * Align examples/syscalls assembler syscall-number names with + cpu/cpu-syscalls.c: use __NR_* consistently. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.9. + * cpu/Makefile.am: rename sysdeps dependency .Tpo files to .Plo. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.8. + * Remove the erroneous top-level dist-hook added while packaging manual + examples; rely on the normal EXTRA_DIST rules so make distcheck does not + overwrite files in Automake's read-only distribution tree. + * Keep every examples subtree except examples/syscalls byte-identical to + the 0.3.4 baseline. + * Rewrite examples/syscalls run scripts as transparent manual demonstrations + without hidden PASS/FAIL tests, and expand its README into book-oriented + documentation of the descriptor-I/O ABI and all four examples. + * Place scratch read buffers in .bss and use .zero only as the supported + NOBITS reservation directive inside .bss. + * Preserve the 0.3.7 object-tool version-test fix and the 0.3.5 syscall ABI. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.7. + * Make object-tool --version runner checks use Automake PACKAGE_VERSION + instead of the historical hard-coded 0.3.4 string. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.6. + * Remove tst-syscall-runner.sh and tst-syscall-io-runner.sh from tests. + * Keep examples/syscalls as a manual user/documentation tree, use installed + mcpu-* tools from PATH, and disable GNU make built-in suffix rules there. + * Replace unsupported assembler label-subtraction immediates in examples + with MCPU PTR64 arithmetic using LEA/PDIFF. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.5. + * Add SYS_read, SYS_write, SYS_lseek, SYS_open and SYS_close using the + MINIX-style VFS number block 0x100..0x105, leaving 0x104 reserved. + * Add a per-cpu_context 256-entry guest descriptor namespace with borrowed + stdin/stdout/stderr and owned descriptors returned by SYS_open. + * Add common PTR64 copyin/copyout/copyinstr validation and strict UTF-8 + pathname validation at the guest/HOST boundary. + * Add Linux/x86_64/LP64 sysdeps wrappers for POSIX open/close/read/write/ + lseek with explicit MCPU-to-HOST flag, mode and whence translation. + * Add examples/syscalls with canonical assembler headers, examples, run + scripts and book-oriented README; install headers under the MCPU root. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.4. + * Add the HOST sysdeps configuration hierarchy for future syscall wrappers, + based on the historical LibMPU preconfigure/sysnames/VPATH model. + * Add architecture preconfigure.ac/preconfigure fragments for i386, PowerPC, + SPARC and x86_64; x86_64 distinguishes x32/ILP32 and 64-bit LP64. + * Add ordered Implies inheritance and GNU make vpath selection through the + substituted sysnames variable, with cpu/sysdeps/generic always last. + * Reject HOST OS/CPU hierarchies not represented by LibMCPU sysdeps instead + of accepting a generic-only configuration. + * Add no syscall wrappers and change no CPU, MELF or SYS_brk semantics. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * LibMCPU 0.3.3: remove tst-version-runner.sh from TESTS and EXTRA_DIST, + and remove the runner itself from the release source tree. + * Keep the centralized --version/--dump-version implementation unchanged; + version reporting may be verified directly from the installed tools. + * No CPU ISA, MELF ABI, linker, loader/runtime or syscall semantic changes. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.2. + * Make tests/tst-version-runner.sh self-contained when CPU_AS, CPU_LD, + CPU_RUN and the other CPU_* tool variables are not exported. Values + supplied by the Automake harness remain authoritative; otherwise the + runner resolves freshly built sibling utilities itself. + * No CPU, MELF, runtime, binary-utility or syscall semantics changed. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.1. + * Normalize --version output for every installed mcpu-* tool as + "<program> (MCPU) <version>" and add --dump-version for a bare version. + * Add utils/mcpu-version.c/.h as the single implementation of version + formatting and link it into all nine command-line utilities. + * Add a common regression for both version forms and synchronize bilingual + manual pages and command help. + * Keep CPU ISA, frozen MELF ABI v3, linker, loader/runtime and syscall + semantics unchanged. + +2026-10-04 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.3.0. + * Start the SYSCALL development stage from the verified LibMCPU 0.2.28 + binary-utility baseline. + * Move English manual-page build/install rules from the top-level + Makefile.am to man/Makefile.am and Russian rules to man/ru/Makefile.am. + * Add man and man/ru to the recursive Automake/configure output tree. + * Keep CPU ISA, frozen MELF ABI v3, linker, loader/runtime, binary-utility + and syscall semantics unchanged. + +2026-10-03 Andrey V. Kosteltsev <support@radix-linux.su> + + * LibMCPU 0.2.28: finalize documentation for the verified 0.2.27 + binary-utility stage without changing CPU/MELF execution semantics. + * Add English and Russian mcpu-*(1) manual pages for every installed + utility and libmcpu(7) overview pages; install localized pages below + the Russian man tree. + * Update both maintained PDF manuals for frozen ABI v3, the complete + binary-utility set and the non-ALLOC debug-metadata compatibility rule. + * Synchronize README.md, NEWS.md and doc/src documentation sources. + +2026-10-03 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.27. + * Add mcpu-objcopy as a MELF-to-MELF structural copy/editor with -j/--only-section, -R/--remove-section, --dump-section, --add-section, --update-section and --rename-section. + * Restrict mutations to safe non-runtime metadata: added sections are non-ALLOC SHT_PROGBITS, update/rename operate only on safe non-ALLOC PROGBITS, and removal/filtering refuses runtime-critical or ET_REL link-critical sections. + * Preserve PT_LOAD contents/program headers and frozen MELF ABI-v3 execution semantics; deliberately omit binary/ihex/srec, VMA/LMA, start-address and section-flag conversion modes. + * Support deterministic static archives member-by-member while preserving their symbol-index semantics; reject ambiguous --dump-section on archive input. + * Refactor the common MELF section parser/rebuilder from mcpu-strip into mcpu-object-edit.c/.h and use it from both mcpu-strip and mcpu-objcopy. + * Add regressions for copy/add/dump/update/rename/remove, protected runtime/link sections, strict --only-section filtering, ET_REL relinking, archive editing and command-line errors. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.26. + * Add mcpu-strip with in-place operation and -o/--output for MELF files and deterministic MCPU archives. + * Remove ordinary non-allocated SHT_SYMTAB and now-unreferenced ordinary string tables only from ET_EXEC/ET_DYN; preserve ET_REL .symtab/.strtab as required link semantics. + * Preserve .dynsym, .dynstr, .dynamic, all retained RELA sections, MCPU target/lifecycle metadata, load/model sections and program headers. + * Rebuild and compact the non-ALLOC metadata tail, rebuild .shstrtab, remap surviving section references and retained symbol st_shndx values, and preserve input file mode. + * Add regressions for current no-op ET_REL/ET_EXEC/ET_DYN/archive cases and for a synthetic valid ET_EXEC carrying removable ordinary .symtab/.strtab metadata; verify stripping shrinks it and leaves execution unchanged. + * Keep frozen MELF ABI-v3, linker/runtime behavior, CPU ISA and archive semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.25. + * Add mcpu-objdump for read-only section headers, full section contents, ordinary/dynamic symbol tables, RELA relocations and MCPU disassembly of MELF ET_REL, ET_EXEC, ET_DYN and UNIX static archives. + * Add -d/-D/-r/-s/-t/-T/-h/-j with GNU-objdump-like meanings; -j is repeatable and filters section-oriented output. + * Add __cpu_decode_at() so inspection code can decode an explicit PROGRAM byte buffer without replacing the live CPU context; retain __cpu_decode() as the runtime wrapper. + * Make disassembly symbol-aware and print retained relocation records beside the instruction that contains each relocation field. + * Continue through undecodable bytes with explicit .byte output rather than guessing instruction boundaries. + * Install mcpu-objdump under the relocatable private MCPU bin directory and create the corresponding public relative symlink. + * Add regressions for the common decoder, section filtering, symbols, dynamic symbols, relocations, disassembly, archives, grouped options and malformed input. + * Keep frozen MELF ABI-v3, assembler encoding, linker/archive semantics, loader/runtime behavior and CPU execution semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.24. + * Add mcpu-nm for read-only symbol inspection of MELF ET_REL, ET_EXEC, ET_DYN and UNIX static archives. + * Read .symtab by default and .dynsym with -D; classify code/data/BSS/read-only/undefined/absolute/common/weak symbols with conventional one-letter codes. + * Add external/undefined filters, lexical/numeric/reverse/no-sort modes, archive/member display and per-line file prefixes. + * Install mcpu-nm under the relocatable private MCPU bin directory with the same public relative-symlink policy as the existing tools. + * Add regressions for ordinary/dynamic symbols, filtering, sorting, ET_EXEC/ET_DYN, static archives, prefixes and malformed input. + * Keep frozen MELF ABI-v3, mcpu-ld, mcpu-ar archive semantics, runtime loader and CPU ISA unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.23. + * Add mcpu-ld --static as a library-selection modifier that forces every -lNAME lookup to use libNAME.a without changing the selected output mode. + * Preserve ABI-v3 --dynamic/--shared output under --static while incorporating selected archive members directly and emitting no DT_NEEDED for them. + * Preserve directory-major -L search order while skipping shared-only candidates under --static and continuing to later directories for libNAME.a. + * Accept --static with ABI-v1 and -r, whose -l lookup is already archive-only, and fail rather than falling back to .so when no archive is available. + * Add dedicated forced-static regressions for ABI-v1, ABI-v3 ET_EXEC/ET_DYN, -r, search order, missing archives and help text. + * Keep frozen MELF ABI-v3, mcpu-ar archive format, runtime loader, dependency resolver, CPU ISA and runtime symbol-scope semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.22. + * Add mcpu-ld -r partial relocatable linking, producing one current MELF ET_REL object from multiple ET_REL inputs. + * Merge like-named sections in input order with alignment, rebase defined symbols and relocation offsets, preserve RELA records, and allow unresolved symbols to remain for a later final link. + * Preserve LOCAL symbol identity across inputs and apply deterministic GLOBAL/WEAK merging; reject duplicate strong GLOBAL definitions during the partial link. + * Allow -r to consume static libNAME.a archives with the existing selective extraction semantics; shared libraries are not eligible in relocatable-link mode. + * Reject final-image-only options and modes with -r, including --dynamic, --shared, STACK/HEAP sizing and --soname. + * Split release history from README.md into NEWS.md and keep README.md as a concise project/build/tool usage introduction. + * Update the Russian MELF ABI manual from doc/src for frozen ABI v3, SONAME/NEEDED dependencies, static archives and mcpu-ld -r. + * Add partial-link regressions covering retained relocations, unresolved references, LOCAL/GLOBAL/WEAK semantics, archive extraction and final-link execution. + * Keep frozen MELF ABI-v3, runtime loader, dependency resolver, CPU ISA and runtime symbol-scope semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.21. + * Teach mcpu-ld to consume UNIX static archives containing current MCPU MELF ET_REL members. + * Allow ABI-v1 -lNAME to resolve libNAME.a; for ABI-v3 --dynamic/--shared search each library directory for libNAME.so first and libNAME.a second. + * Extract only archive members needed by currently unresolved strong GLOBAL symbols; unresolved WEAK symbols do not trigger extraction. + * Recompute unresolved symbols after each extraction and rescan the same archive until stable, including backward dependencies on earlier archive members. + * Preserve one-pass ordering across separate -l operands; repeating an archive request can satisfy references introduced by intervening libraries. + * Keep statically extracted members out of DT_NEEDED while preserving existing SONAME validation and dependency recording for selected ET_DYN providers. + * Parse archive member bytes directly with the strict ET_REL reader rather than via temporary files, and keep correctness independent of the optional archive symbol index. + * Add regressions for indexed/non-indexed archives, selective extraction, intra-archive rescans, WEAK behavior, inter-library order, ABI-v1 archive-only lookup and ABI-v3 .so/.a directory precedence. + * Keep frozen MELF ABI-v3, runtime loader, CPU ISA, relocation and runtime symbol-scope semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.20. + * Add mcpu-ar as a deterministic UNIX static-archive manager for current MCPU MELF ET_REL object modules. + * Support r/q/d/t/p/x operations, standalone s, and c/s/v/D modifiers with traditional combined operation strings such as rcs. + * Preserve member order; r replaces in place, q appends, and only validated MCPU ET_REL members are accepted. + * Support short names, BSD #1/<length> extended names, and reading GNU // long-name tables with /offset references. + * Write a real System-V/GNU-style archive symbol index containing defined GLOBAL/WEAK MCPU symbols; preserve/rebuild an existing index after archive mutations. + * Make archive output reproducible by using deterministic member metadata and add regressions for create/list/print/extract/replace/append/delete/index/long-name/error behavior. + * Install mcpu-ar under the relocatable MCPU private bin directory with the same public relative-symlink policy as the other MCPU utilities. + * Keep mcpu-ld archive consumption, static -l fallback and partial relocatable linking deferred to later releases; frozen MELF ABI3 and runtime behavior are unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.19. + * Replace mcpu-as historical #line source-location parsing with preprocessing line markers of the form # N, # N "filename" and # N "filename" flags..., with decimal N in 0..2147483647 and trailing flags ignored. + * Ignore #pragma and #lang without advancing the logical diagnostic line counter; discard all other #... lines as assembler syntax while counting them as ordinary logical source lines. + * Keep preprocessor source-control details out of mcpu-as --help and document them in the technical documentation instead. + * Print each mcpu-readelf Section Headers entry on one wide physical line instead of wrapping every section into two lines; keep -W/--wide accepted for compatibility. + * Add regressions for line-marker flags, filename retention, empty lines, #pragma/#lang counting, generic # lines, obsolete #line behavior and one-line section-header formatting. + * Keep the frozen MELF ABI-v3, CPU ISA, linker, loader, dependency resolver, relocation and runtime symbol-scope semantics unchanged. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.18. + * Freeze the completed MELF ABI-v3 wire/runtime dependency contract after the staged SONAME/NEEDED, link-time validation, recursive loading and deterministic symbol-scope work in 0.2.14--0.2.17. + * Add MCPU_LD_PRELOAD as the environment counterpart of mcpu-run --ldpreload; environment entries are resolved first and command-line entries are appended after them. + * Preserve rightmost-higher-priority explicit-preload semantics, so equal-strength command-line preloads naturally override environment preloads. + * Keep MCPU_LD_PRELOAD independent of mcpu-ld.conf, --no-config and --sys-root; it uses the same effective library resolver after search-path/configuration policy has been established. + * Make the regression environment explicitly start with an empty MCPU_LD_PRELOAD and add dedicated environment-preload and ABI-v3 freeze/end-to-end regressions. + * Keep ABI-v1/ABI-v2 loader compatibility and make no CPU ISA, MELF-v3 wire-format, SONAME/NEEDED, -L/-l, search-path, relocation or symbol-scope changes. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.17. + * Define deterministic ABI-v3 runtime symbol scope for mcpu-run: GLOBAL outranks WEAK; explicit --ldpreload is the highest equal-strength scope and remains rightmost-higher-priority; the main executable outranks normal dependencies; normal DT_NEEDED scope is breadth-first and left-to-right. + * Replace recursive DFS dependency ordering with a stable breadth-first SONAME queue while preserving cycle termination and deduplication. + * Pass the explicit-preload boundary separately from the resolved module list so the low-level loader does not infer scope classes from incidental path order. + * Preserve the historical flat-preload behavior of direct cpu_dyn_load() compatibility callers while mcpu-run uses the scoped resolved loader path. + * Add regressions for main/dependency priority, direct dependency order, explicit-preload override, GLOBAL/WEAK precedence, WEAK-only ordering, direct/transitive scope and diamond dependency graphs. + * Keep MELF ABI version 3 and make no on-disk format, search-path or link-time -l changes. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.16. + * Add recursive runtime resolution of ABI-v3 DT_NEEDED dependencies to mcpu-run using the existing MCPU library search policy. + * Resolve each dependency by SONAME, validate the selected ABI-v3 ET_DYN image and require its DT_SONAME to match the requested DT_NEEDED identity exactly. + * Resolve transitive dependencies recursively, deduplicate already registered SONAMEs and terminate dependency cycles without duplicate loads. + * Keep filesystem/configuration search policy in a host-side dependency resolver and pass only the completed module list to a separate resolved low-level load path. + * Load normal dependencies before explicit --ldpreload modules so the established explicit-preload override behavior remains intact; an ABI-v3 preload with a matching SONAME may satisfy a normal dependency. + * Preserve the legacy no--l plus explicit --ldpreload workflow unchanged. + * Add regressions for direct and transitive NEEDED loading, missing transitive dependencies, SONAME mismatch, dependency cycles and preload override of a normal dependency. + * Convert the maintained pendulum example to the normal ABI-v3 dependency workflow with a librk4 SONAME symlink chain, -lrk4 link and no --ldpreload at runtime. + * Keep MELF ABI version 3; ordinary dependency-graph symbol-scope policy remains a development item for 0.2.17. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.15. + * Add mcpu-ld -l NAME / -lNAME for ABI-v3 dynamic outputs; -lNAME searches libNAME.so through explicit -L directories followed by the effective MCPU system library path. + * Validate each selected provider as a complete ABI-v3 ET_DYN image, read its mandatory DT_SONAME and record that SONAME rather than the development link name or physical pathname in DT_NEEDED. + * Validate strong undefined imports of ABI-v3 ET_EXEC against the exports of explicitly linked libraries before output is written; unresolved WEAK imports keep their existing optional semantics. + * Deduplicate repeated direct dependencies by SONAME while preserving first-request order. + * Permit ABI-v3 ET_DYN outputs to record direct NEEDED dependencies as well; ABI-v1 static output rejects -l. + * Keep the verified explicit-preload workflow unchanged when no -l operands are supplied; recursive DT_NEEDED runtime loading remains deferred to 0.2.16. + * Add regressions for SONAME symlink layouts, -L priority, config-based library lookup, missing/malformed providers, missing exports, duplicate SONAMEs and the 0.2.16 runtime boundary. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.14. + * Introduce development MELF ABI-v3 dependency metadata while preserving ABI-v1 and ABI-v2 loader compatibility. + * Define SHT_DYNAMIC/.dynamic and the 16-byte MElf64_Dyn wire format with DT_NULL, DT_NEEDED and DT_SONAME tags linked to .dynstr. + * Make new mcpu-ld --dynamic/--shared outputs ABI-v3; require --soname=NAME for ABI-v3 ET_DYN and reject pathname SONAMEs. + * Keep the historical ABI-v2 writer internally for compatibility regressions. + * Extend mcpu-run dynamic parsing to accept ABI-v2 and ABI-v3, validate canonical .dynamic metadata and reject DT_NEEDED explicitly until recursive dependency loading is implemented. + * Add mcpu-readelf -d/--dynamic and include dynamic metadata in -a. + * Update dynamic/shared examples and regressions for ABI-v3 SONAME, ABI-v2 compatibility, ABI-v3 explicit preload and negative SONAME cases. + * No recursive dependency loading, -l handling or NEEDED symbol validation is introduced in this release. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.13. + * Add a common host-side MCPU library-search resolver shared by mcpu-ld and mcpu-run. + * Add --library-path=DIR[:DIR...] / --library-path DIR[:DIR...] to mcpu-run; repeated options append explicit search directories in command-line order. + * Resolve bare --ldpreload names through explicit directories followed by the effective MCPU_LD_SYSTEM_LIBRARY_PATH; pathnames containing '/' keep the historical direct-path behavior. + * Add -L DIR and -LDIR to mcpu-ld so linker search state and --print-search-dirs use the same common ordering policy. + * Keep -l deliberately unsupported until dependency metadata can give it real linker/NEEDED semantics. + * Preserve existing --ldpreload list order and rightmost-higher-priority interposition after name resolution. + * Add regressions for system/configured lookup, explicit-path priority, search-order changes, empty system paths, missing libraries, invalid path lists and mcpu-ld -L diagnostics. + * No CPU ISA, MELF ABI, SONAME/NEEDED, relocation or dynamic-loader semantics change in this release. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.12. + * Add --dump-config and --print-search-dirs diagnostic information actions to mcpu-ld and mcpu-run through the common mcpu_tool_options layer. + * --dump-config reports the resolved runtime root, configuration enable/disable state, config-file read/not-found state, and effective MCPU_LD_SYSTEM_LIBRARY_PATH. + * --print-search-dirs reports the effective MCPU library search order; an empty configured system path yields an empty list without fallback. + * Diagnostic actions require no linker input or executable and honor --no-config and --sys-root exactly as normal execution does. + * Add regressions for identical mcpu-ld/mcpu-run diagnostics, user overrides, empty search paths, malformed config handling and public help. + * No CPU ISA, MELF ABI, library-resolution, NEEDED or existing dynamic-linking semantics change in this release. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.11. + * Add a common relocatable MCPU runtime/configuration layer for mcpu-ld and mcpu-run. + * Derive the default MCPU root from the real executable and use <runtime-root>/lib as the default MCPU system library tree. + * Add the mcpu-ld.conf hierarchy <runtime-root>/etc -> /etc/mcpu -> $HOME/.mcpu/etc, with MCPU_LD_SYSTEM_LIBRARY_PATH as the effective system-library override. + * Install only <runtime-root>/etc/mcpu-ld.conf; keep /etc/mcpu and per-user configuration under administrator/user control. + * Add --no-config and --sys-root=PATH/--sys-root PATH to mcpu-ld and mcpu-run. --sys-root implies --no-config and accepts absolute or invocation-relative paths. + * Introduce mcpu_tool_options plus dedicated mcpu_ld_options and mcpu_run_options structures; pass parsed state explicitly through const option pointers to linker/runner execution components. + * Add regressions for user-config location, old-path rejection, empty overrides, malformed-config suppression, absolute/relative sys-root and real mcpu-ld/mcpu-run behavior. + * No CPU ISA, MELF ABI, library-search, NEEDED, or existing dynamic-linking semantics change in this release. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.10. + * Add mcpu-readelf, a read-only MELF ABI1/ABI2 inspection utility for ET_REL, ET_EXEC and ET_DYN files. + * Add -h/-l/-S/-s/-r/-A views for MELF file headers, program headers, section headers, symbol tables, RELA relocations and MCPU target/lifecycle metadata; add -e and -a aggregate modes plus GNU-readelf-like long aliases. + * Install mcpu-readelf under $libdir/mcpu/bin and create the corresponding public symlink in $bindir beside mcpu-as, mcpu-ld and mcpu-run. + * Add a readelf regression covering ET_REL, ABI-v2 ET_EXEC, ET_DYN, symbols, retained relocations, architecture metadata, --help/--version and rejection of non-MELF input. + * No CPU ISA, MELF ABI, assembler, linker, loader or runtime semantics changed from 0.2.9. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.9. + * Rework the Autoconf/Automake bootstrap following the MCPU project convention: project-owned macros live in acsite.m4, m4/ is reserved for external/vendor macros, and bootstrap regenerates Libtool/Autotools files while copying system macro packages. + * Replace pkg-config dependency checks with AC_CHECK_LIBMPU(1.0.25) and AC_CHECK_LIBMPUIO(1.0.4), using LIBMPU_* and LIBMPUIO_* substitutions supplied by the system macro packages. + * Install mcpu-as, mcpu-ld and mcpu-run under $libdir/mcpu/bin and create relative public symlinks in $bindir; libmcpu, public headers and pkg-config metadata keep their existing installation locations. + * Add the make tests convenience target alongside make check and add configuration headlines, summary output and post-configure usage help. + * No CPU ISA, MELF ABI, assembler, linker, loader or runtime semantics changed from 0.2.8. + +2026-10-02 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.8. + * Normalize C source and header formatting to the established LibMPU project style. + * Keep dense initializer tables compact where they are intentionally tabular. + * No functional changes: CPU ISA, MELF ABI, assembler, linker, loader, runtime, tests and examples are unchanged from 0.2.7. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.7. + * Documentation-only cosmetic correction of 0.2.6. + * Fix overlapping entries in the MELF canonical-section-name list. + * Raise the SP line in architecture Figure 9 so it no longer crosses the label text. + * Remove generated Markdown companions from doc/src/ and from the documentation build. + * CPU, assembler, linker, loader, runtime, tests and examples are unchanged from 0.2.6. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.6. + * Documentation-only release. + * Restyled both maintained manuals with the memoir/Roboto book style. + * Replaced text pseudo-diagrams with vector TikZ figures and restored real tables. + * Added the HLL/CRT PROGRAM and MODEL contracts for main(), init(), life(), and optional fini(). + * Added Markdown companions under doc/src/. + * CPU, assembler, linker, loader, runtime, tests and examples are unchanged from 0.2.5. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.5. + * Replace the rigid `mcpu-as -c input.s output.o` interface with the normal UNIX-style `mcpu-as -o output.o input.s` command line. + * Permit the single input source before or after `-o`; keep `-h/--help` and reject obsolete `-c` as an unknown option. + * Update tests, examples, README and both maintained manuals to the new assembler syntax; CPU ISA and MELF ABI are unchanged. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.4. + * Documentation-only release; keep library, assembler, linker, loader, runtime, tests, examples, ISA and MELF ABI unchanged from the user-verified 0.2.3 baseline. + * Rework doc/libmcpu-bytecode-architecture-ru.pdf using the visual language of the LibCPU programming book sources: DejaVu Serif/Sans typography, proper tables and TikZ diagrams for STACK/SP, CALL argument blocks, FP, stack frames and dynamic stack scopes. + * Restore tabular presentation of instruction/ABI layouts that had regressed into code-like pseudo-tables. + * Rewrite doc/libmcpu-melf-abi-ru.pdf predominantly in Russian while retaining established technical terms, MELF identifiers and ABI names where appropriate. + * Keep all editable XeLaTeX sources in doc/src/ and update them for LibMCPU 0.2.4. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.3. + * Regression-test path correction only; keep the library, assembler, linker, loader, runtime, MELF ABI, documentation and examples unchanged from 0.2.2. + * Update tests/tst-dynamic-runner.sh to load dynamic-main.s, dynamic-liba.s and dynamic-libb.s from examples/shared-libs/ after the 0.2.2 examples reorganization. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.2. + * Examples/documentation-layout release only; keep cpu/, utils/, include/ and tests/ implementation sources unchanged from the verified 0.2.1 release. + * Move editable documentation sources and build-docs.sh from doc/ to doc/src/, add doc/src/README, and leave only the maintained PDF manuals directly under doc/. + * Add build/run help to every assembler example source. + * Move dynamic-liba.s, dynamic-libb.s and dynamic-main.s to examples/shared-libs/, replace the former examples/_as.sh, examples/_ld.sh and examples/_run.sh workflow with examples/shared-libs/Makefile. + * Move check-sys-brk.s and the three _ld_check-sys-brk-*.sh scripts to examples/sys-brk/ and adjust their utility paths to ../../utils/. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.1. + * Documentation-and-examples release only; keep cpu/, utils/, include/ and tests/ implementation sources unchanged from the verified 0.2.0 release. + * Replace doc/libmcpu-bytecode-architecture-ru.pdf with a new standalone current architecture/ABI reference and distribute its XeLaTeX source plus generated opcode/RCONST tables. + * Replace doc/libmcpu-melf-abi-ru.pdf with a new standalone MELF reference whose section numbering starts at 1 and which integrates static ABI v1, startup-dynamic ABI v2, lifecycle, STACK/HEAP/startup contracts and the corrected LOCAL ET_DYN runtime-relocation rule. + * Supersede the temporary 0.2.0 MELF erratum because its normative correction is now incorporated directly into both maintained PDF manuals. + * Add doc/build-docs.sh and shared TeX style source so both PDF manuals are reproducibly rebuilt with XeLaTeX. + * Add the user-verified examples/pendulum/ experiment unchanged from the supplied examples.tar.xz: pendulum ET_EXEC plus rk4.so ET_DYN, Makefile/build wrapper and TRACE experiment script. + +2026-09-24 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.2.0. + * Correct MELF ABI-v2 ET_DYN local relocation semantics: references to LOCAL symbols inside a shared object remain non-preemptible, but are retained as LOCAL runtime relocations so the startup linker can add the final runtime base of the referenced PROGRAM or DATA segment. + * Emit LOCAL ET_DYN relocation targets in .dynsym ahead of GLOBAL/WEAK symbols and set the .dynsym sh_info boundary accordingly. LOCAL dynamic symbols are never admitted to the interposition namespace. + * Teach the startup dynamic loader to accept LOCAL .dynsym entries and resolve LOCAL runtime relocations strictly against the originating module instead of performing GLOBAL/WEAK winner lookup. + * Add low-level PROGRAM/DATA rebasing regression coverage to tst-dynamic and an assembler/linker/loader end-to-end regression with an exported ET_DYN entry calling a private local helper after non-zero module placement. + * Add doc/libmcpu-melf-abi-0.2.0-erratum-ru.txt as the normative correction to the pre-0.2.0 PDF wording for LOCAL ET_DYN relocations. + * Preserve ABI-v1 static linking and the established ABI-v2 GLOBAL/WEAK interposition rules; MELF ABI version remains 2 because this release corrects the implementation of the existing ET_DYN runtime-address contract rather than introducing a new container ABI. + +2026-09-22 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.9. + * Add mcpu-run lifecycle stage actions --before-run, --after-init, --on-step and --after-run without changing the MCPU ISA or MELF ABI; existing END boundaries provide INIT/LIFE/FINI synchronization points and BEFORE_RUN is host-side. + * Add typed comma-separated scalar/register print and set actions for registers and DATA/CONTROL/HEAP/INPUT/OUTPUT/TRACE. One print action emits its scalar values on one line separated by spaces; repeated stage options remain ordered and supported. + * Parse set-int-* and set-real-* values through LibMPUIO mpu_sscanf(), validate a whole action before applying its writes, and preserve guest PSW across host-side numeric parsing. + * Keep raw stage access read-only: print-raw-* accepts exactly one dump per action, supports the existing binary raw-size suffixes and boundary clipping, and no set-raw-* actions are provided. + * Add the explicit mcpu-run -- separator: the preferred syntax is "mcpu-run [runtime-options] -- executable [program-args...]" while the previous no-separator form remains compatible. Tokens after -- are passed to the guest argv unchanged. + * Add examples/life-print.s for two-register one-line output after every LIFE END and examples/argc-argv.s for argc/argv inspection with option-looking guest arguments. + * Add end-to-end regression coverage for stage ordering, empty INIT stage behavior, LIFE actions, FINI-before-AFTER_RUN ordering, typed set/print lists, explicit -- argv separation, raw-list rejection and the deliberate absence of set-raw-*. + * Document mcpu-run lifecycle test-bench actions, explicit argv separation and their relationship to MELF lifecycle boundaries in both bundled PDF documents. + +2026-09-22 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.8. + * Extend mcpu-run integer, real and raw-memory inspection to CONTROL, HEAP, INPUT, OUTPUT and TRACE in addition to the existing DATA/register output options. + * Add binary K/KiB, M/MiB and G/GiB suffixes to the <bytes> operand of every mcpu-run --raw-* option, with overflow checks and clipping at the selected memory/section boundary. + * Keep --raw-data bounded by the linked .data section while CONTROL/INPUT/OUTPUT/TRACE raw dumps are bounded by their segment sizes and HEAP raw dumps by the prepared HEAP size. + * Align the mcpu-run --help option descriptions in one vertical column and document the --raw-* binary size suffixes there. + * Add end-to-end regression coverage for integer/real inspection of DATA, CONTROL, HEAP, INPUT, OUTPUT and TRACE, all six raw-size suffix spellings, clipping, boundary rejection and overflow rejection. + * Add the verified SYS_brk examples check-sys-brk.s and the grow/limit/shrink link-and-run scripts; keep the legacy MELF ABI-v1 main-basic invocation commented out in examples/_run.sh. + +2026-09-22 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.7. + * Add the first MCPU system-call ABI with the zero-operand SYSCALL opcode and fixed overlapping-register syscall area at the top of the register file. + * Add SYS_brk as system call number 10, dispatched inside LibMCPU through a local table of function pointers; unimplemented system-call numbers return -ENOSYS. + * Add _heap_brk as the architectural upper boundary of guest-accessible HEAP memory and implement internal __cpu_heap_brk() with immutable _heap_limit, transactional host-backing growth, stable PTR64 offsets and deterministic zeroing of released memory on shrink. + * Define linker-owned _end as the non-preemptible executable HEAP pointer HEAP:16; _end is also the initial and minimum program break. + * Extend mcpu-ld size parsing with binary K/KiB/M/MiB/G/GiB suffixes for STACK/HEAP options. + * Document the HEAP/program-break model, _end, SYSCALL register ABI, SYS_brk semantics and future -host_errno syscall convention in both bundled PDF documents. + * Add unit and end-to-end regressions for SYSCALL/SYS_brk, HEAP shrink/regrowth zeroing, host-backing growth, immutable HEAP limits, overlapping syscall registers, linker-defined _end in static/dynamic ET_EXEC, and binary size suffixes. + +2026-09-22 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.6. + * Edit the bundled PDF documentation for the LibMCPU rename: replace legacy LibCPU/libcpu.h/cpu-elf.h and cpu-as/cpu-ld/cpu-run references with LibMCPU/libmcpu.h/mcpu-elf.h and mcpu-as/mcpu-ld/mcpu-run, and update tools/cpu-* source paths to utils/mcpu-* where they appear. + * No library, ISA, MELF ABI, assembler, linker, loader or runtime semantics changed from 0.1.5. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.4. + * Packaging-only release; preserve the working 0.1.3 assembler, linker, loader, runtime, ABI and tests source code unchanged. + * Regenerate the distributed Autotools files after the version bump and ensure generated files are newer than their input files, so an ordinary release build does not try to invoke aclocal/automake/autoconf. + * No MELF ABI v1/v2, ISA, CALL ABI, #line, dynamic-linking or runtime semantics changed from 0.1.3. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.3. + * Fix mcpu-as #line logical-line accounting across physically empty source lines. + * Keep the assembler core diagnostic counter synchronized with physical source lines by preserving empty lines as whitespace-only parser lines; assembly semantics are unchanged. + * Add tst-line-runner.sh regressions for native physical-line counting and the reported preprocessor shape: #line 4 "dyn.s", an empty line, .text, a label, then an error must report dyn.s:7. + * Document that the logical #line counter advances across empty and comment-only lines. + * No MELF ABI v1/v2, ISA, CALL ABI, dynamic-linking or runtime semantics changed from 0.1.2. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.2. + * Fix tests/tst-immediate-exactness-runner.sh for the source-location diagnostics introduced in 0.1.1: mcpu-as errors now have the form "mcpu-as: file:line: error: ...", so the regression checks the source filename and line number instead of the obsolete contiguous "mcpu-as: error:" prefix. + * No assembler, MELF ABI, ISA, dynamic-linking or runtime semantics changed from 0.1.1. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.1. + * Add mcpu-as -h/--help with a detailed assembler/source-location help page. + * Add mcpu-as #line decimal source-location directives for future preprocessor output. + * #line controls the logical line of the immediately following source line and optionally changes the diagnostic filename; omitting the filename preserves the previous one. + * Parse #line numbers strictly in decimal even with leading zeroes and accept the C99/C++11 range 0..2147483647. + * Do not add __LINE__/__FILE__ assembler symbols; #line affects diagnostics only and is accepted for both .s and .S input names. + * Add runner regression coverage for --help, filename/line remapping, decimal leading zeroes, zero and range rejection. + * No MELF ABI v1/v2, ISA, CALL ABI, dynamic-linking or runtime semantics changed from 0.1.0. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.1.0. + * Add MELF ABI version 2 startup dynamic linking while retaining ABI-v1 static ET_EXEC compatibility. + * Add mcpu-ld --shared for ET_DYN helper libraries and mcpu-ld --dynamic for ABI-v2 ET_EXEC. + * ABI-v2 ET_DYN permits only .text/.data/.bss allocated content and has no lifecycle, model segments, STACK or HEAP. + * Add .dynsym/.dynstr as the runtime symbol ABI, separate from optional developer .symtab/.strtab information. + * Retain GLOBAL/WEAK runtime RELA records in ABI-v2 outputs; LOCAL and lifecycle references remain statically bound. + * Add mcpu-run --ldpreload=a.so:b.so explicit colon-separated preload list; no DT_NEEDED, implicit search paths, PLT/GOT, lazy binding or dlopen API. + * Implement startup interposition immediately: strong GLOBAL beats WEAK; among equal bindings the rightmost preload wins, including preemption of definitions already present in main. + * Keep _start/_life/_fini executable-private and non-preemptible. + * Shared libraries have per-segment relative PROGRAM/DATA images; mcpu-run assigns final process bases and preserves one process-owned DATA NULL guard. + * Permit explicit cross-library and cyclic symbol resolution because all preload modules are parsed before runtime RELA application. + * Add dynamic-link regression coverage, shared/dynamic examples and strict --shared STACK/HEAP-option rejection. + * Extend both doc/*.pdf references with a detailed MELF ABI-v2 startup-linking chapter and preserve the complete architecture PDF outline. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.25. + * Add mcpu-run --int-data=<bits>@<addr> to print a signed integer object from DATA memory. + * Add mcpu-run --raw-data=<bytes>@<addr> to dump bytes from the linked .data section with a 16-byte hexadecimal/ASCII view. + * Raw DATA dumps are clipped at the end of .data and never read into .bss or other DATA-segment bytes. + * Extend runner regression coverage for integer DATA output, raw dump formatting and .data-bound clipping. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.24. + * Add the missing <stdint.h> include to tests/tst-melf.c so UINT64_MAX is declared explicitly. + * Add -h/--help handling and detailed option help to mcpu-ld, matching mcpu-run style. + * Add lifecycle-main-model.s demonstrating .init, CONTROL/INPUT/OUTPUT/TRACE, McpuStartup argc/argv handling and a normal main() that increments a counter by 2. + * Add lifecycle-main-basic.s demonstrating the same _life -> main(argc, argv) bridge with _start_life: and ordinary DATA, without .init or model segments. + * No MELF ABI or CPU execution semantics changed from 0.0.23. + +2026-09-21 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.23. + * Correct MELF target endian selection to use LibMPU boolean byte/word-order selector macros; MPU_BYTE_ORDER/MPU_WORD_ORDER themselves are numeric order tags (for example 1234), not 0/1 selectors. + * Replaced the temporary LCPUOBJ1/raw executable path with the permanent MELF (MCPU ELF-derived) static object/executable ABI. + * Added public include/mcpu-elf.h covering MELF identification, headers, sections, symbols, RELA relocations, target/lifecycle metadata, Program Headers and loader/startup contract. + * mcpu-as -c now emits MELF64 ET_REL with .symtab/.strtab/.shstrtab, SHT_RELA, .mcpu.target and section-relative symbols. + * Added .weak symbol declarations and ELF-style strong/weak resolution; unresolved weak pointer relocations with zero addend resolve to NULL. + * Added real SHT_NOBITS .bss support; linker places .bss after file-backed DATA and preserves contribution alignment without storing zero payload bytes. + * mcpu-ld now consumes MELF ET_REL, applies all five fixed relocation kinds, inserts the single DATA NULL guard and emits complete MELF ET_EXEC images. + * Added mandatory _start and _life ET_EXEC lifecycle contract, optional _fini and assembler-only _start_life: shorthand for coincident _start/_life when .init is absent. + * Added .mcpu.target and .mcpu.lifecycle metadata validation and full PTR64 sh_addr/st_value/p_vaddr semantics. + * Added MELF Program Headers for PROGRAM/DATA/CONTROL/INPUT/OUTPUT/TRACE plus PT_MCPU_STACK and optional PT_MCPU_HEAP with explicit p_maxsz. + * Removed mcpu-run STACK/HEAP sizing overrides; STACK/HEAP sizes are executable ABI selected by mcpu-ld and recorded in Program Headers. + * Added persistent McpuStartup ABI with version, argc, argv and guest PTR64 argument strings in the high end of STACK. + * mcpu-run now validates MELF ET_EXEC before allocating guest memory and follows the rule 'loader validates, never repairs'. + * Added MELF ET_REL/ET_EXEC, BSS, startup, lifecycle, weak-symbol, relocation and malformed-loader regression coverage. + * Updated assembler examples for the mandatory lifecycle ABI and MELF toolchain. + * Expanded doc/*.pdf with the MELF ABI, linker/loader contract, startup ABI and 0.0.23 implementation model. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.22. + * Added an explicit relocatable-object/linker model before fixing the final ELF representation. + * Added .global/.globl and .extern symbol declarations; ordinary labels remain local unless exported. + * Added .ptr for generic target-representation PTR64 data objects; .cptr remains the PROGRAM-pointer form. + * Added relocation semantics for CODE64, DATA ADDR64, LEA PTR64 immediates and PTR64 data objects. + * Relocatable expressions are restricted to SYMBOL + signed constant addend; absolute arithmetic remains assembler-evaluated. + * Added per-object section alignment so linker concatenation preserves leading .align requirements. + * Moved the DATA NULL guard responsibility to the linker for relocatable objects: each object DATA starts at offset zero, the final image gets one 16-byte guard. + * Added temporary .lco development relocatable container with canonical MSB-first metadata and raw architectural section payloads. + * Added mcpu-as -c and mcpu-ld development tools; mcpu-ld emits the existing raw executable sidecars for mcpu-run. + * Added cross-object global/extern resolution, duplicate/undefined-symbol diagnostics, relocation application and lifecycle entry resolution. + * Added relocatable/linker regression coverage and a two-module example returning 42. + * Expanded the architecture PDF with symbols, bindings, addends, relocation types, alignment, DATA guard ownership, linker algorithm and future ELF mapping. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.21. + * Assign PTR64 selectors 0x3..0x7 to CONTROL, INPUT, OUTPUT, TRACE and HEAP; selectors 0x8..0xF remain reserved. + * Add independent anonymous mmap-backed CONTROL/INPUT/OUTPUT/TRACE/HEAP regions with guest access permissions. + * Define TRACE as the current telemetry frame; accumulated history belongs to the runtime collector. + * Add .control/.input/.output/.trace data sections and .init/.fini executable sections; .init/.text/.fini share one PROGRAM address space. + * Define lifecycle ABI entries _start, _life and _fini and expose their numeric raw-image metadata. + * Add END for cooperative phase completion and EXIT r32 for graceful final termination, plus explicit stop-reason/exit-status API. + * Generalize LEA to emit a complete PTR64 immediate for data-like and PROGRAM labels without changing its binary size. + * Add runtime HEAP SEG=0x7 with a 16-byte low guard, initial size and maximum limit; allocator and dynamic-growth services remain library/runtime concerns. + * Extend mcpu-run with --heap-size, --heap-limit, --init-only and --steps and add a model lifecycle example/regression. + * Keep the emitted .meta file a temporary raw-development manifest, not the final object/executable format. + * Expand the architecture PDF with the model-memory map, lifecycle state/entry ABI, END/EXIT/HALT/fault distinctions, permissions, LEA PTR64 behavior and future heap allocator design. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.20. + * Fix assembler source encoding as UTF-8. + * Add .ucs2 for strict UTF-8-to-UCS-2 conversion with target-endian + 16-bit DATA objects and a terminating zero code unit. + * Add locale-independent .utf8 for exact UTF-8 bytes plus terminating zero. + * Add raw .byte directive and .int as an alias of .integer. + * Extend .integer/.int to accept value lists. + * Add section-independent .equ absolute symbols with forward .equ references. + * Add integer arithmetic expressions with +, -, *, /, %, unary +/- and + parentheses; DATA/CODE address fixups accept section-label expressions. + * Add symbolic register addresses such as r64@table_ptr and expression + addresses such as r64@(table_ptr+8). + * Add assembler directives/expressions regression coverage and example. + * Expand the architecture PDF with the assembler/preprocessor boundary, + string encodings, expression grammar and directive semantics. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.19. + * Enforce exact assembler immediate semantics for LDI/LDR/LDC. + * LDR r32 literals must be exactly representable as Real32; r64 and + wider destinations must be exactly representable as Real64. + * LDC components must be exactly representable as Real32; c64 remains + the minimum complex destination width. + * LDI rejects signed-value loss in destinations narrower than 64 bits + and values outside signed Integer64 immediate range. + * Diagnostics direct wider/higher-precision constants to DATA directives + and LOAD. + * Add immediate-exactness regression tests and documentation. + * Preserve PSW[15:0] around LibMPU conversion helpers used by LDI/LDR/LDC, + ICVT/RCVT, integer<->Real conversion, CCVT and CRTOC. + * Add conversion-flag regression coverage so conversion implementation + details cannot clobber guest status flags. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.18. + * Audited LibMCPU opcodes against the configured LibMPU 1.0.25 public value-level API. + * Added all remaining non-modular integer shifts/rotates, INC/DEC/XCHG, copy/convert, full multiply, quotient+remainder divide and bit-length instructions. + * Extended decoded instructions to four operands for IDIV/ISDIV. + * Added missing Real sign/copy/compare, decomposition, rounding, remainder, log1p/expm1, inverse hyperbolic, integer/Real conversion and classification instructions. + * Expanded RCONST with EPSILON, InD, signed NaN/Inf/min/max and signed-zero generators while retaining the complete mathematical constant family. + * Added Complex real/imag extraction, Real-to-Complex, general/polar construction, conjugate, abs/norm/arg, equality predicates and copy/convert instructions. + * Deliberately excluded modular arithmetic, textual conversion, Real-format metadata queries and LibMPU runtime/flag accessors from the CPU ISA. + * Added direct LibMPU-vs-LibMCPU coverage regression and an executable assembler audit example. + * Expanded the architecture PDF with the complete coverage matrix, instruction forms, width rules and assembler examples. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.17. + * Fixed PTR64 comparison conventions: full-width equality and same-selector PDIFF ordering. + * Fixed the normal stack ABI red-zone size at zero bytes. + * Fixed reserved PTR64 selector rules for non-dereferencing versus address-space operations. + * Added generic BSWAP raw-byte reversal for equal-width r8..r65536 register views. + * BSWAP preserves PSW and uses source-snapshot semantics for in-place operation. + * Added pointer-policy, reserved-segment and BSWAP regression coverage plus a runner example. + * Expanded the architecture PDF with normative pointer comparison, red-zone, reserved-segment and external-byte-order guidance. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.16. + * Fixed the split byte-order ABI: PROGRAM fields are canonical MSB-first, architectural data objects follow the configured LibMPU target representation. + * Added __cpu_code_get_u16/u32/u64 and __cpu_code_put_u16/u32/u64 helpers for canonical bytecode fields. + * Added __cpu_data_get_u16/u32/u64 and __cpu_data_put_u16/u32/u64 helpers for target data-object representation. + * Exposed LibMCPU target byte-order/word-order aliases derived from LibMPU in libmcpu.h. + * Reworked decoder, pointer/stack/control-flow object accesses and assembler fixups to use the explicit code/data helper domains. + * Made .cptr the canonical assembler directive for PROGRAM PTR64 DATA objects; retained .codeptr as a compatibility alias. + * Added byte-order/object-representation regression coverage for IMM64, integer/real/complex DATA objects, PTR64, CALL return PC and LOAD/BMOV/LOAD byte preservation. + * Expanded the architecture PDF with the normative byte-order/object-representation model and assembler examples. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.15. + * Fixed the normal-function PSW calling convention without adding PSW-writing opcodes. + * Defined PSW[15:0] (all LibMPU arithmetic/status flags) as volatile across CALL. + * Defined TF, IF and MODE as persistent control state that a normal callee must preserve. + * Classified HF as execution state outside the ordinary function-call save/restore contract. + * Kept CALL, CALLP and RET themselves PSW-neutral: they perform no hidden PSW save/restore. + * Added public ABI masks for volatile status, persistent control and execution-state PSW domains. + * Added PSW call-ABI regression tests and an assembler example showing callee flag clobbering. + * Expanded the architecture PDF with normative PSW calling-convention rules and examples. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.14. + * Added JMPP r64(PROGRAM PTR64) indirect jump instruction (opcode 0x4003). + * Added CALLP r64(PROGRAM PTR64) indirect call instruction (opcode 0x4012). + * CALLP pushes the same raw 64-bit next-PC CODE64 used by direct CALL; RET is unchanged. + * Added atomic PROGRAM-pointer/range/stack validation for indirect control flow. + * Added assembler JMPP/CALLP syntax with physical r64 operand checks. + * Added .codeptr CODE-label DATA directive for PROGRAM PTR64 tables and forward fixups. + * Added indirect-control assembler example covering CALLP and JMPP through a DATA table. + * Added decoder/executor/fault/atomicity/.codeptr regression coverage and runner test. + * Expanded the architecture PDF with indirect control-flow formats, detailed state transitions and jump-table pattern. + +2026-09-20 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.13. + * Replaced malloc-backed PROGRAM/DATA/STACK buffers with independent anonymous mmap() mappings. + * Added separate internal architectural size and page-rounded host mapping capacity for every segment. + * Added __cpu_program_protect(); PROGRAM is writable while loading and read-only after protection. + * mcpu-run now loads .code/.data with open/fstat/read into anonymous mappings; image files are never mmap()ed. + * Added 16-byte ABI maximum natural alignment, stack/call alignment and argument-block alignment constants. + * Defined NULL PTR64 as all zero and reserved DATA[0..15] as the assembler NULL guard. + * LOADP/STOREP/BMOV dereference of NULL now raises CPU_EXEC_POINTER_FAULT. + * The assembler reserves the 16-byte DATA NULL guard and retains explicit-only .align semantics with no implicit directive alignment. + * Updated ABI examples to use 16-byte call argument blocks where required and changed the real4096 example from .align 512 to ABI-natural .align 16. + * Added anonymous-mmap/page-alignment/PROGRAM-protection regression coverage. + * Expanded the architecture PDF with host-backing rules, DATA NULL guard, alignment/data-layout rules and formal argument-block layout. + * HEAP is explicitly outside DATA and remains reserved for a future independent memory segment. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.12. + * Added memory-to-memory block-copy instructions BMOV and BMOVI. + * BMOV uses a runtime unsigned r64 byte count; BMOVI embeds an unsigned IMM64 byte count. + * Block copies use memmove semantics, including overlap inside one architectural segment. + * DATA and STACK are readable/writable block-copy spaces; PROGRAM is readable but never writable. + * Added complete-range validation before modification, preserving destination memory on faults. + * Added block-copy assembler support, example and regression tests. + * Expanded the architecture PDF with compiler lowering for struct assignment, by-value aggregates and VLA/runtime copies. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.11. + * Added dedicated 64-bit architectural FP as a STACK byte offset. + * Empty allocated STACK now initializes SP = FP = stack_size. + * Added PUSHFP, SETFP, RESTSP and POPFP frame-management instructions. + * Added LDFP, STFP and LEAFP signed FP-relative addressing instructions. + * Added SETSP for installing a validated STACK PTR64 as the dynamic stack top. + * Added atomic bounds/fault handling for FP/SP management operations. + * Added optional-FP prologue/epilogue and dynamic stack allocation ABI rules. + * Added frame-pointer/dynamic-stack assembler example and regression tests. + * Expanded the architecture PDF with compiler-oriented frame/VLA examples and diagrams. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.10. + * Added PTR64 byte-offset arithmetic: PADD, PADDI, PSUB, PSUBI and PDIFF. + * Pointer arithmetic preserves the 4-bit segment selector and operates only on the 60-bit offset. + * Added signed OFF64 immediate forms and signed r64 displacement forms. + * Added same-segment signed byte difference with PDIFF. + * Added atomic 60-bit pointer overflow/underflow checks and PSW preservation. + * Switched assembler signed-64 parsing paths to LibMPU fixed __mpu_int64_t values. + * Added pointer-arithmetic assembler example and regression tests. + * Updated the architecture PDF with normative pointer-arithmetic semantics and diagrams. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.9. + * Added PTR64 segmented virtual pointers: 4-bit segment + 60-bit byte offset. + * Defined DATA, STACK and read-only PROGRAM pointer segments; reserved 0x3..0xf. + * Added LEA, LEASP, LOADP and STOREP machine instructions and assembler syntax. + * Added public PTR64 construction/inspection helpers and pointer-fault status. + * Fixed the C-like ABI for scalar/pointer/aggregate arguments and stack locals. + * Fixed hidden PTR64 caller-provided storage for struct/union return values. + * Added pointer and aggregate ABI examples and regression tests. + * Rebuilt the architecture PDF with detailed PTR64 and C-like ABI diagrams. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.8. + * Added CALL CODE64 and RET machine instructions. + * CALL pushes the 64-bit next PC on STACK; RET pops it back to PC. + * Added right-to-left stack argument convention and caller cleanup ABI. + * Added 8 KiB Return Value Area at 0x7e000..0x7ffff. + * Added canonical ABI return-register helper and public return-area constants. + * Added CALL/RET assembler support, example and regression tests. + * Updated the architecture PDF with detailed CALL ABI diagrams and examples. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.7. + * Added malloc-backed STACK memory and downward-growing SP semantics. + * Added signed 64-bit stack displacement encoding (OFF64). + * Added PUSH, POP, LDSP, STSP and ADJSP machine instructions. + * Added STACK bounds checking with atomic stack faults. + * Added stack syntax to the test assembler and stack regression tests. + * Added --int-reg and --stack-size options to mcpu-run. + * Updated the architecture PDF with STACK, SP and SP-relative addressing. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.6. + * Added 64-bit PROGRAM branch address encoding (CODE64). + * Added JMP, JSET and JCLR machine instructions. + * Added PSWBIT8 selectors for individual PSW flags. + * Added forward/backward CODE labels to the test assembler. + * Added JZ/JNZ/JC/JNC/JO/JNO/JS/JNS assembler aliases. + * Updated __cpu_step() PC handling for taken branches. + * Added control-flow example and regression test. + * Updated the architecture PDF with CODE64 and branch semantics. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.5. + * Require LibMPU 1.0.25 or later. + * Added mcpu-run utility for loading and executing .code/.data images. + * Added LibMPUIO 1.0.3 dependency for mcpu-run real-number output. + * Added --real-reg, --real-data and --digits mcpu-run options. + * Added 4096-bit 2.0 + PI assembler/runner example and regression test. + +2026-09-19 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.4. + * Added REG24, OPCODE16, IMM64 and ADDR64 machine encodings. + * Added malloc-backed PROGRAM and DATA memory to the CPU context. + * Added bytecode decoder, single-step and run execution. + * Added LDI, LDR, LDC, LOAD, STORE and RCONST. + * Added real and complex LibMPU-backed instruction families. + * Added source-level c<bits>@addr complex views with physical width semantics. + * Added test assembler and DATA directives for integer, real and complex constants. + * Added detailed bytecode/operand architecture PDF in doc/. + * Added bytecode and assembler regression tests. + +2026-09-18 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.3. + * Added decoded instruction and operand representation. + * Added common instruction executor. + * Added NOP, HALT, MOV and CLR instructions. + * Added IADD, IADC, ISUB and ISBB integer instructions using LibMPU backend. + * Added instruction/operand/register validation in the executor. + * Added instruction and integer FLAGS regression test. + +2026-09-18 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.2. + * Added physical register-view resolver for r8 ... r65536. + * Added register width, natural-alignment and bounds validation. + * Added register-file width/alignment/bounds regression test. + * Added overlapping register-view regression test. + +2026-09-18 Andrey V. Kosteltsev <support@radix-linux.su> + + * Version 0.0.1. + * Added thread-local CPU context with 512 KiB register file. + * Added PC, SP and logical 32-bit PSW integration with LibMPU flags. + * Added HF, TF, IF and MODE control/status fields. + * Added context/PSW regression test. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..8831955 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,69 @@ + +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = cpu utils sysroot tests man + +include_HEADERS = include/libmcpu.h include/mcpu-elf.h + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = mcpu.pc +bin_SCRIPTS = mcpu-config + +aclocaldir = $(datadir)/aclocal +aclocal_DATA = libmcpu.m4 + +mcpuasmheadersdir = @MCPU_ROOT@/include/sys/asm + +dist_mcpuasmheaders_DATA = \ + sysroot/include/sys/asm/syscall.h \ + sysroot/include/sys/asm/fcntl.h \ + sysroot/include/sys/asm/unistd.h \ + sysroot/include/sys/asm/types.h \ + sysroot/include/sys/asm/stat.h \ + sysroot/include/sys/asm/dirent.h \ + sysroot/include/sys/asm/select.h \ + sysroot/include/sys/asm/time.h \ + sysroot/include/sys/asm/errno.h \ + sysroot/include/sys/asm/mpu-errno.h + +EXTRA_DIST = \ + AUTHORS \ + ChangeLog \ + LICENSE \ + NEWS.md \ + README.md \ + acsite.m4 \ + bootstrap \ + libmcpu.m4 \ + config \ + cpu/sysdeps \ + m4/README \ + sysroot/README \ + doc/libmcpu-bytecode-architecture-ru.pdf \ + doc/libmcpu-melf-abi-ru.pdf \ + doc/src/README \ + doc/src/build-docs.sh \ + doc/src/libmcpu-bytecode-architecture-ru.tex \ + doc/src/libmcpu-hll-crt-contract-ru.tex \ + doc/src/libmcpu-architecture-core-ru.tex \ + doc/src/libmcpu-architecture-runtime-ru.tex \ + doc/src/libmcpu-isa-coverage-ru.tex \ + doc/src/libmcpu-assembler-details-ru.tex \ + doc/src/libmcpu-melf-abi-ru.tex \ + doc/src/libmcpu-doc-common.tex \ + doc/src/libmcpu-assembler-directives-table.tex \ + doc/src/libmcpu-opcode-table.tex \ + doc/src/libmcpu-psw-table.tex \ + doc/src/libmcpu-rconst-table.tex + + +.PHONY: tests +tests: all + $(MAKE) $(AM_MAKEFLAGS) -C tests check-TESTS + +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +libtool: $(LIBTOOL_DEPS) + $(SHELL) $(top_builddir)/config.status $(top_builddir)/libtool + +distclean-local: + -rm -rf $(top_srcdir)/autom4te.cache @@ -0,0 +1,208 @@ + +# LibMCPU 0.4.0 + +LibMCPU 0.4.0 is the first public version. + +# LibMCPU 0.3.35 + +LibMCPU 0.3.35 extends the MCPU CPU/system ISA with explicit writable arithmetic/status flag control. The new opcodes are `SETF=0x0010`, `CLRF=0x0011`, `CLRCPUF=0x0012`, and `CLRMPUF=0x0013`. `SETF flag` and `CLRF flag` accept only the writable PSW selectors AF through INXF (bits 0..15), changing exactly one selected bit while preserving every other PSW bit. HF, TF, IF and MODE remain outside this write contract; attempts to assemble or decode `SETF/CLRF` for HF/TF/IF are rejected. + +The arithmetic/status domain is now documented as two architectural byte ranges: CPU flags in `PSW[7:0]` and MPU mathematical flags in `PSW[15:8]`. `CLRCPUF` clears only mask `0x000000ff`; `CLRMPUF` clears only mask `0x0000ff00`; both preserve the other flag byte and all execution/control state above bit 15. `JSET/JCLR` keep their existing wider read-only selector contract. Existing LibMPU semantics of `IADC`, `ISBB`, and rotate-through-carry operations are unchanged; the new instructions provide a direct way to establish their input flag state. + +Assembler, decoder/executor, and `mcpu-objdump` are updated consistently. Regression coverage verifies opcode values, single-flag preservation, CPU/MPU bulk-clear masks, rejection of control-state writes, explicit carry input for IADC/ISBB, assembler encoding/decoding, and objdump disassembly. The maintained architecture PDF sources/PDF and the English/Russian `mcpu-as(1)` and `libmcpu(7)` manual pages document the new contract. MELF, syscall, HLL Runtime ABI, CRT, linker, loader and runtime lifecycle contracts are otherwise unchanged. + +# LibMCPU 0.3.34 + +LibMCPU 0.3.34 is a documentation-only production-readiness release over the verified 0.3.33 baseline. The two XeLaTeX reference manuals are made release-number-independent and are updated to the current signed HLL Runtime ABI joining mcpu-ld, CRT and MCPU Libc. The HLL manual now documents fixed-width int32/int64/PTR64 signatures, frontend normalization of fini, automatic PROGRAM/MODEL classification and CRT insertion, McpuStartup argc/argv ownership, the four reserved Libc hooks, PRE_RUNTIME/RUNNING behavior, MODEL life return values using `-__INT32_MAX__ - 1`, graceful finalization and dynamic non-interposition rules. + +The bytecode manual gains permanent reference appendices for the full mcpu-as directive set, every public OPCODE16 with operands and a Russian explanation, all branchable PSW selectors, and the existing RCONST selectors. Stale release-history sections and references to examples bundled inside LibMCPU are removed; examples are now a separate package. The MELF manual is likewise detached from LibMCPU package release numbers and corrected to describe HLL classification as an mcpu-ld layer above the language-neutral MELF wire format. Syscall documentation now states the stable host-independent MCPU errno contract rather than the historical host-errno plan. + +There is no MCPU ISA, MELF ABI, syscall ABI, HLL Runtime ABI, CRT, linker, loader, runtime or command-line semantic change in this release. + +# LibMCPU 0.3.33 + +LibMCPU 0.3.33 is a documentation/production-readiness follow-up to the user-verified 0.3.32 baseline. It does not change the MCPU ISA, MELF ABI, syscall ABI, HLL Runtime ABI v1, CRT implementation, linker behavior, loader/runtime behavior, or command-line semantics. + +The top-level README is converted from a release-note-like document into a version-independent description of the current source tree and installed interfaces. Release-specific history remains in NEWS.md and ChangeLog. Build instructions no longer contain a release-numbered source path, version-output examples use a symbolic package-version placeholder, and the README now documents the current HLL complete-program object contract, automatic PROGRAM/MODEL CRT selection, required CRT installation path, McpuStartup/CALL bridge, MODEL life status namespace, and graceful HLL finalization. + +All English and Russian manual pages now use the version-independent source label `LibMCPU` in their `.TH` headers instead of embedding a package release number. The installed utility `--version` / `--dump-version` interface is the authoritative package-version source. This removes the recurring risk of stale man-page release numbers during otherwise unrelated releases. + +The `libmcpu(7)`, `mcpu-ld(1)`, `mcpu-run(1)`, and `mcpu-as(1)` pages are updated for the signed HLL Runtime ABI v1 and the current package boundary. They describe low-level versus HLL lifecycle ownership, normalized PROGRAM/MODEL symbols, automatic CRT insertion from `<MCPU-root>/lib/mcpu`, fatal behavior when required CRT objects are unavailable, persistent McpuStartup argc/argv, HLL CALL/RET boundaries, MODEL life return semantics, PRE_RUNTIME/RUNNING finalization behavior, and the fact that the assembler itself neither classifies HLL programs nor synthesizes CRT. + +A new regression enforces the documentation version policy: README.md and installed man-page sources must not acquire release-bound LibMCPU semantic-version strings, and every man page must keep the version-independent `LibMCPU` `.TH` source field. + +# LibMCPU 0.3.32 + +LibMCPU 0.3.32 is a production-polish release over the verified 0.3.31 package baseline. Successful `mcpu-as` and `mcpu-ld` invocations are now quiet by default; the existing developer object summaries are preserved unchanged behind the new `-v` / `--verbose` option. `mcpu-ld` applies verbose reporting consistently to partial ET_REL output, ABI-v1 static ET_EXEC, ABI-v3 dynamic ET_EXEC and ABI-v3 ET_DYN output. + +The command-line help text of the installed MCPU utilities is normalized into a visually stable two-column form: option spellings are separated from their explanations by `-`, continuation lines begin at the explanation text column, and the `mcpu-run` Stage actions list is indented by four spaces. No option semantics other than the new assembler/linker verbose switch are changed. + +A dedicated regression verifies quiet defaults plus both `-v` and `--verbose` spellings for `mcpu-as` and all `mcpu-ld` output classes. The English and Russian `mcpu-as(1)` / `mcpu-ld(1)` pages document the new behavior. There is no MCPU ISA, MELF ABI, syscall ABI, HLL Runtime ABI, CRT, linker output-format or runtime semantic change in this release. + +# LibMCPU 0.3.31 + +LibMCPU 0.3.31 is a source-layout cleanup release over the verified 0.3.30 HLL Runtime ABI baseline. The complete `examples/` tree is removed from the LibMCPU distribution; manual and book examples are now intentionally owned by a separate package rather than by the CPU/runtime implementation release. + +The canonical `mcpu-ld.conf` source moves from top-level `etc/` to `sysroot/etc/`, matching the existing rule that `sysroot/` is the build-time image of the target MCPU environment. Its installed location and runtime lookup contract are unchanged: LibMCPU still installs it as `<MCPU-root>/etc/mcpu-ld.conf`. + +The obsolete development-only `sysroot/usr/src/mcpu/libc-stub/` and `sysroot/usr/src/mcpu/hll-emul/` trees are removed. The small assembler inputs required by the HLL/CRT regression suite now live under `tests/fixtures/hll/`, so tests remain self-contained without placing test emulators in the target sysroot. + +There is no MCPU ISA, MELF ABI, syscall ABI, HLL Runtime ABI, CRT behavior, linker/runtime semantics, or installed-path change in this release. It is the clean LibMCPU package boundary for future development: implementation + target sysroot + regression tests + documentation, with examples distributed separately. + +# LibMCPU 0.3.30 + +LibMCPU 0.3.30 fixes the dynamic-MCPU-Libc side of the signed HLL Runtime ABI v1. Internal runtime hooks (`__mcpu_libc_init`, `__mcpu_init_array`, `__mcpu_fini_array`, `__mcpu_libc_fini`) may now be exported by a normal ABI-v3 ET_DYN MCPU Libc dependency and resolved by an HLL dynamic executable. Explicit preload modules are still forbidden from interposing those four hooks. HLL complete-program entry functions and MELF lifecycle symbols retain their existing non-preemptible rules. No existing manual example is changed. + +# LibMCPU 0.3.29 + +LibMCPU 0.3.29 is a small development-convenience follow-up to the verified 0.3.28 HLL Runtime ABI implementation. It adds standalone Makefiles under `sysroot/usr/src/mcpu/libc-stub/` and `sysroot/usr/src/mcpu/hll-emul/`. The libc-stub Makefile builds the temporary `libc-stub.o`; the HLL emulator Makefile builds the PROGRAM and MODEL compiler-output emulator objects and links runnable `program-main` and `model-pendulum-life` executables through the installed CRT, while allowing an alternate MCPU sysroot through `MCPU_LDFLAGS`. These Makefiles remain development-only source artifacts: neither libc-stub nor hll-emul outputs are installed. The existing `examples/` tree, including `examples/pendulum/`, is unchanged. + +# LibMCPU 0.3.28 + +LibMCPU 0.3.28 implements the first concrete stage of the signed MCPU HLL Runtime ABI v1 without changing any existing manual example. The release adds the canonical CRT sources under `sysroot/usr/src/mcpu/crt/`: `crt0.S` adapts PROGRAM executables to `main`, `crt1.S` adapts MODEL executables to `init`/`life`, `crti.S` performs common `McpuStartup` validation and runtime initialization, and `crtn.S` provides mandatory HLL finalization. The generated `crt0.o`, `crt1.o`, `crti.o`, and `crtn.o` are built out of tree and installed under the active MCPU system root at `lib/mcpu/`. + +The existing `mcpu-ld` now recognizes compiler-normalized HLL defining objects automatically, with no `--program` or `--model` switch. A PROGRAM defining ET_REL contains strong GLOBAL/FUNC `main` and `fini`; a MODEL defining ET_REL contains strong GLOBAL/FUNC `init`, `life`, and `fini`, all in the same object. The linker rejects incomplete or split complete-program definitions and mixing raw `_start`/`_life` lifecycle ownership with HLL roots, then injects the appropriate CRT for final ET_EXEC links. HLL roots, lifecycle symbols, CRT-private names, and the internal CRT/Libc runtime-hook bindings are kept non-preemptible in ABI-v3 executables. + +The CRT uses the existing persistent `McpuStartup` area created by the loader. It validates the v1 descriptor before Libc initialization, checks that the unsigned startup `argc` is representable by the signed target `int64` HLL ABI, preserves the existing PTR64 `argv`, and builds the ordinary two-slot CALL ABI argument block for `main(int64 argc, PTR64 argv)` or `init(int64 argc, PTR64 argv)`. The internal runtime-hook ABI is `void __mcpu_libc_init(void)`, `void __mcpu_init_array(void)`, `void __mcpu_fini_array(void)`, and `void __mcpu_libc_fini(void)`; Libc does not build or own `argc`/`argv`. + +The release also implements the signed PRE_RUNTIME/RUNNING distinction. Startup validation failures use the normal graceful `EXIT` path while CRT state is still PRE_RUNTIME; `_fini` therefore performs only `END` and does not call user `fini` or Libc finalization hooks. Once startup hooks complete, normal PROGRAM/MODEL exits run `fini`, `__mcpu_fini_array`, `__mcpu_libc_fini`, then `END` while preserving the original exit status. MODEL `life()` uses the fixed int32 namespace: `-2147483648` means continue through one `END`, zero means natural `EXIT(0)`, positive values are error exit statuses, and the remaining negative values stay reserved. + +Until the separate MCPU Libc and `mcpu-cc` projects exist, regression-only sources live beside the CRT under `sysroot/usr/src/mcpu/`. `libc-stub/` emulates the four internal Libc hooks and is never installed. `hll-emul/` contains PROGRAM and pendulum-shaped MODEL assembler fixtures that emulate future compiler output while defining no MELF lifecycle symbols. The MODEL fixture does not include, depend on, or modify `examples/pendulum/`. The complete pre-existing `examples/` tree remains byte-identical to LibMCPU 0.3.27. + +The existing ET_DYN ownership rule is retained: shared objects may allocate only ordinary `.text`, `.data`, and `.bss`; `.init`, `.fini`, `.control`, `.input`, `.output`, `.trace`, lifecycle ownership, STACK, and HEAP remain properties of the main ET_EXEC. New end-to-end regressions cover automatic CRT insertion, PROGRAM and MODEL lifecycle behavior, startup failure, hook ordering, ABI-v3 non-interposition, and the ET_DYN restrictions. + +# LibMCPU 0.3.27 + +LibMCPU 0.3.27 extends the verified 0.3.26 descriptor baseline with `dup` (`__NR_dup=0x126`) and `dup2` (`__NR_dup2=0x127`). Both calls operate entirely in the per-`cpu_context` guest descriptor namespace: HOST descriptor numbers remain private implementation details. `dup` returns the lowest available guest descriptor, including a previously closed standard guest slot, and returns `-EMFILE` when no guest slot is available. + +`dup2(oldfd,newfd)` validates the source before changing the target, rejects target values outside 0..255 with `-EBADF`, returns `newfd` unchanged when `oldfd == newfd`, and otherwise replaces exactly the requested guest descriptor. A real duplication clears guest descriptor flags on the new descriptor, while the `oldfd == newfd` no-op preserves them. Replacement of borrowed guest stdin/stdout/stderr never closes the supervisor process HOST stdio. The common layer intentionally does not call HOST `dup2()`: it first creates an independent HOST duplicate with the new `__cpu_sys_dup()` adapter and then binds that HOST descriptor to the requested guest slot. + +A new `tst-dup` regression checks lowest-free allocation, exact-target replacement, descriptor-flag semantics, `oldfd == newfd`, reuse of closed guest fd 0, stable `-EBADF`, and preservation of an occupied target when the source is invalid. `examples/syscalls/` adds manual `dup-basic` and `dup2-basic` demonstrations and the canonical `sysroot/include/sys/asm/syscall.h` plus the standalone example snapshot publish the two new syscall numbers. No other `examples/` subtree is changed. + +# LibMCPU 0.3.26 + +LibMCPU 0.3.26 is a control/layout release with no MCPU ABI change. The canonical public assembler ABI headers move from the manual examples tree to `sysroot/include/sys/asm/`. Top-level installation now takes `sys/asm/*.h` only from this canonical sysroot tree, and `tests/tst-errno-abi` validates the public errno/MPU errno headers through `-I$(top_srcdir)/sysroot/include`, so regression tests no longer depend on `examples/`. + +`examples/syscalls/include/sys/asm/` remains a byte-identical standalone snapshot for the manual/book syscall examples. A maintainer-only `make sync-example-syscall-headers` target copies canonical headers from `sysroot/` to that snapshot; normal `make`, `make check`, `make dist`, and `make install` do not invoke the sync operation. This establishes the one-way ownership rule `sysroot -> examples` and allows the examples tree to be split into a separate distribution later without moving or reconstructing the public MCPU ABI headers. + +The control release also removes the remaining regression-test dependency on the manual `examples/` tree. Assembly inputs that had historically been reused by test runners are copied unchanged into `tests/fixtures/`, including the dynamic shared-library fixtures, and every affected runner now resolves those files below `TEST_SRCDIR/fixtures`. The manual examples themselves are unchanged. Therefore `make check` no longer consumes files from `examples/`; examples remain user/book material only. + +# LibMCPU 0.3.25 + +LibMCPU 0.3.25 replaces the historical negative HOST-errno syscall ABI with a stable HOST-independent MCPU error namespace. System-call failures now return `-MCPU_errno`; `SYS_brk` retains its previously signed special return contract. The public assembler header `sys/asm/errno.h` fixes the MCPU values through `EFTYPE=134`, preserves `EWOULDBLOCK=EAGAIN`, `EDEADLOCK=EDEADLK`, and `ENOTSUP=EOPNOTSUPP`, reserves 135..4094 for future ABI growth, and defines `EUNKNOWN=4095` for an unknown or unmapped HOST error. + +All existing common-layer error paths now use internal `__MCPU_E*` constants. Every selected HOST syscall adapter translates a real HOST failure through `__cpu_errno_from_host()` before returning to common MCPU code, including the direct positive error-number convention of HOST `clock_nanosleep()`. Raw HOST errno values therefore stop at the sysdeps boundary. The translator maps symbolic HOST errno names rather than assuming numeric identity, accepts HOST aliases whether or not their numbers coincide, maps zero to zero, and falls back to `__MCPU_EUNKNOWN` for an invalid or unknown value. + +The release also adds `sys/asm/mpu-errno.h`, which publishes the LibMPU 1.0.25 error names and values unchanged. For MCPU users, MPU is documented as the conceptual Math Processor Unit coprocessor with independent integer, real, complex, and math error-state domains. MPU error state is not automatically converted to system errno; that mapping remains future guest-libc policy. Two regression programs verify the public/internal MCPU errno contract, HOST translation including aliases and fallback, and the fixed MPU constants. No new syscall, signal, socket, timer, or ISA operation is introduced. + +# LibMCPU 0.3.24 + +LibMCPU 0.3.24 extends the verified 0.3.23 time ABI with `nanosleep` (`__NR_nanosleep=0x030`) and `clock_nanosleep` (`__NR_clock_nanosleep=0x031`). Both calls reuse the fixed 16-byte signed-64-bit MCPU `timespec`; valid sleep values require `tv_sec >= 0` and `0 <= tv_nsec < 1000000000`. `nanosleep` is implemented in the common syscall layer as a relative `CLOCK_MONOTONIC` wait over the single new fundamental HOST adapter `__cpu_sys_clock_nanosleep()`. + +`clock_nanosleep` supports only the existing MCPU `CLOCK_REALTIME=0` and `CLOCK_MONOTONIC=1` namespace and adds the HOST-independent `TIMER_ABSTIME=1` flag. Relative mode validates writable `rem` before blocking and writes it only when the HOST wait is interrupted with `EINTR`. Absolute mode treats `req` as a clock deadline and deliberately ignores `rem` completely, including pointer validation. The Linux/x86_64/LP64 adapter explicitly maps clock IDs and `TIMER_ABSTIME`, checks signed 64-bit seconds against HOST `time_t`, and normalizes the libc `clock_nanosleep()` direct error-number convention into LibMCPU's usual negative HOST errno ABI. + +`examples/syscalls/` adds manual `nanosleep-relative` and `clock-nanosleep-absolute` demonstrations. The first exposes before/after `CLOCK_MONOTONIC` timestamps using `--after-run=print-int-data:64@16,64@24,64@32,64@40`; the second demonstrates an absolute monotonic deadline one second in the future. The syscall README explains interruption/remainder semantics and why absolute deadlines avoid cumulative period drift. No signal, POSIX timer, time-setting, CPU-time clock, or Linux-specific clock interface is added. No other `examples/` subtree and no regression test is changed. + +# LibMCPU 0.3.23 + +LibMCPU 0.3.23 extends the verified 0.3.22 syscall baseline with the first time-reading block: `gettimeofday` (`__NR_gettimeofday=0x01c`), `clock_getres` (`__NR_clock_getres=0x021`), and `clock_gettime` (`__NR_clock_gettime=0x022`). The MCPU ABI defines fixed 16-byte `timeval` and `timespec` objects containing signed 64-bit seconds plus a separate signed 64-bit fractional field. HOST `time_t`, `long`, `struct timeval`, and `struct timespec` never cross the guest boundary. + +The initial MCPU clock namespace contains only `CLOCK_REALTIME=0` and `CLOCK_MONOTONIC=1`. The Linux/x86_64/LP64 sysdeps adapters explicitly translate those IDs to HOST clocks and reject every unsupported ID with `-EINVAL`. `clock_gettime` requires a non-NULL writable guest timespec pointer; `clock_getres` permits a NULL result pointer while still validating the clock ID. `gettimeofday` remains a two-argument compatibility ABI, requires a non-NULL timeval pointer and a NULL obsolete timezone pointer, and is implemented in the common syscall layer by reading MCPU `CLOCK_REALTIME` and converting nanoseconds to microseconds. No independent HOST `gettimeofday` wrapper is added. + +`examples/syscalls/include/sys/asm/time.h` publishes the fixed layouts and clock IDs, while `select.h` now imports that shared timeval definition without changing the existing select ABI. Manual `gettimeofday`, `clock-gettime`, and `clock-getres` examples expose the returned two-field structures through `mcpu-run --int-data=64@16,64@24`, and the syscall README documents the clock semantics, 64-bit range, HOST conversion boundary, and why CPU-time clocks and time-setting calls are intentionally outside this release. No other `examples/` subtree and no regression test is changed. + +# LibMCPU 0.3.22 + +LibMCPU 0.3.22 extends the verified 0.3.21 syscall baseline with a current-working-directory state owned by each `cpu_context`. The new calls are `chdir` (`__NR_chdir=0x108`) and `fchdir` (`__NR_fchdir=0x125`). Neither call proxies HOST `chdir()` or HOST `fchdir()`: the Linux/x86_64/LP64 implementation stores an independent `O_PATH|O_DIRECTORY` directory capability in the MCPU context, so changing a guest cwd never changes the process-global cwd of `mcpu-run`. + +All existing pathname syscalls that accept relative names now resolve them against this context-local directory by using the corresponding Linux `*at()` interfaces. This covers `open`, `creat`, `link`, `unlink`, `rename`, `mkdir`, `chmod`, `chown`, `access`, `rmdir`, `symlink` link-path resolution, `readlink`, `stat`, `lstat`, and pathname `truncate`. Absolute pathnames continue to behave normally. Since Linux has no `truncateat`, the selected adapter implements context-relative pathname `truncate` as `openat` followed by `ftruncate`, without changing the guest ABI. + +`fchdir` resolves a guest descriptor through the per-context fd table and creates a separate directory capability for cwd state. The original guest descriptor may therefore be closed immediately after `fchdir` without invalidating the current directory. `examples/syscalls/` adds manual `chdir-open` and `fchdir-open` demonstrations and the README documents context isolation, relative pathname resolution, lifecycle ownership, and the distinction between guest cwd and the supervisor process cwd. No other `examples/` subtree and no regression test is changed. + +# LibMCPU 0.3.21 + +LibMCPU 0.3.21 extends the verified 0.3.20 descriptor baseline with `select` (`__NR_select=0x11e`). The guest ABI uses a fixed 32-byte descriptor set containing four target-order u64 words, covering all 256 guest descriptor numbers, and an optional fixed 16-byte input-only timeout containing signed 64-bit seconds and microseconds. HOST `fd_set`, HOST `timeval`, and HOST descriptor numbers are never exposed. + +The common syscall layer validates every selected guest descriptor below `nfds`, resolves it through the per-`cpu_context` descriptor table, and overwrites only the supplied guest sets with ready bits on success. `nfds` is limited to 0..256, invalid guest descriptors return `-EBADF`, invalid timeout values return `-EINVAL`, and a NULL timeout means an unlimited wait. The timeout is deliberately input-only so the guest ABI does not inherit HOST-specific timeout-update behavior. + +The Linux/x86_64/LP64 adapter uses `ppoll()` internally rather than HOST `fd_set`. This avoids HOST `FD_SETSIZE` limitations and keeps readiness translation explicit: read, write, and exceptional conditions are mapped into MCPU readiness classes. `examples/syscalls/` adds the manual `select-pipe` demonstration and `sys/asm/select.h`; the guest creates a pipe, writes data, waits for the read end with select, prints the bytes, and exposes the returned ready-bit count through DATA:16. No other `examples/` subtree and no regression test is changed. + +# LibMCPU 0.3.20 + +LibMCPU 0.3.20 extends the verified 0.3.19 descriptor baseline with `pipe2` (`__NR_pipe2=0x11a`). The guest ABI writes two u64 guest descriptor numbers into a fixed 16-byte output array: the read end at offset 0 and the write end at offset 8. The pair is allocated atomically in the per-`cpu_context` descriptor namespace and HOST fd numbers are never exposed. + +The supported MCPU flags are `O_NONBLOCK` and `O_CLOEXEC`. `O_NONBLOCK` is explicitly translated by the Linux/x86_64/LP64 sysdeps adapter. `O_CLOEXEC` is deliberately kept as guest descriptor state (`FD_CLOEXEC`) on both new descriptors and is not proxied to HOST `FD_CLOEXEC`, preserving the future guest `exec` contract established by `fcntl`. Unknown flag bits return `-EINVAL`; inability to allocate two guest descriptor slots returns `-EMFILE`. + +`examples/syscalls/` adds the manual `pipe2-basic` demonstration. The guest writes a line through the write end, reads it from the read end, prints the returned bytes, and exposes `F_GETFD` through DATA:16 so the reader can see that `O_CLOEXEC` became guest `FD_CLOEXEC=1`. The README documents the 16-byte pipefd layout, rollback rules and flag translation. No other `examples/` subtree and no regression test is changed. + +# LibMCPU 0.3.19 + +LibMCPU 0.3.19 extends the verified 0.3.18 filesystem baseline with `fcntl` (`__NR_fcntl=0x119`). The initial HOST-independent MCPU command set is `F_DUPFD=0`, `F_GETFD=1`, `F_SETFD=2`, `F_GETFL=3`, and `F_SETFL=4`; `FD_CLOEXEC=1` is a guest descriptor flag. + +`F_DUPFD` duplicates the underlying HOST file description but allocates the returned descriptor in the per-`cpu_context` guest namespace at or above the guest-provided minimum. The new guest descriptor owns its duplicated HOST fd and starts with descriptor flags cleared. `F_GETFD`/`F_SETFD` operate only on guest descriptor flags; they deliberately do not proxy HOST `FD_CLOEXEC`, so future guest `exec` semantics remain independent from the supervisor process. + +`F_GETFL` explicitly translates HOST access mode, `O_APPEND`, and `O_NONBLOCK` into MCPU flags. `F_SETFL` accepts the MCPU access-mode bits for the conventional GETFL/SETFL cycle but only changes `O_APPEND` and `O_NONBLOCK`; creation-time flags are rejected with `-EINVAL`. HOST `F_*` and `O_*` numbers are never exposed as MCPU ABI. + +`examples/syscalls/` adds manual `fcntl-getfl`, `fcntl-setfl`, `fcntl-dupfd`, and `fcntl-fdflags` demonstrations. The README explains descriptor flags versus file-status flags, guest descriptor duplication, the future role of `FD_CLOEXEC`, and the exact three-argument MCPU syscall ABI. No other `examples/` subtree is changed. + +# LibMCPU 0.3.18 + +LibMCPU 0.3.18 extends the verified 0.3.17 filesystem baseline with `getdents` (`__NR_getdents=0x11d`) and a fixed 288-byte `struct __cpu_dirent` guest ABI. HOST directory layouts are never copied into guest memory. The Linux/x86_64/LP64 adapter translates inode, directory cookie, entry type and UTF-8 name into fixed records; returned byte counts are always whole-record multiples, and zero means end of directory. + +`examples/syscalls/` adds a manual `getdents-dir` demonstration and `sys/asm/dirent.h`. The guest itself walks the returned records and prints each `d_name`; the README documents the complete layout, UTF-8 boundary, fixed record size and directory-order semantics. No other `examples/` subtree is changed. + +# LibMCPU 0.3.17 + +LibMCPU 0.3.17 extends the verified 0.3.16 filesystem baseline with file-length and synchronization calls: `truncate`, `ftruncate`, and `fsync`. Their MINIX3-inspired VFS numbers are `__NR_fsync=0x120`, `__NR_truncate=0x121`, and `__NR_ftruncate=0x122`. + +`truncate(path,length)` accepts a strict NUL-terminated UTF-8 pathname and a signed 64-bit guest length. `ftruncate(fd,length)` resolves the per-context guest descriptor before entering the HOST adapter. Both adapters require the guest length to be exactly representable by HOST `off_t`; an unrepresentable value returns `-EOVERFLOW`, while filesystem and negative-length policy remains the responsibility of the HOST kernel and is reported as `-host_errno`. + +`fsync(fd)` also resolves a guest descriptor through the current `cpu_context` and forwards an explicit synchronization request to the selected HOST backend. It introduces no new guest structure or HOST typedef into the ABI. + +`examples/syscalls/` adds manual `truncate-file`, `ftruncate-file`, and `fsync-file` demonstrations. The first two show observable file sizes before and after the guest operation; the fsync example demonstrates `open -> write -> fsync -> close` and the README explains why durability itself is not a distinct content transformation that can be displayed after a normal run. No other `examples/` subtree is changed. + +# LibMCPU 0.3.16 + +LibMCPU 0.3.16 extends the verified 0.3.15 filesystem baseline with metadata modification calls: `chmod`, `fchmod`, `chown`, and `fchown`. Their MINIX3-inspired VFS numbers are `__NR_chmod=0x10b`, `__NR_chown=0x10c`, `__NR_fchmod=0x123`, and `__NR_fchown=0x124`. + +`chmod` and `fchmod` reuse the existing HOST-independent MCPU permission bits and explicitly translate them into HOST `mode_t`. `fchmod` resolves the guest descriptor through the per-context guest-fd table before entering the HOST adapter. + +The ownership ABI uses 64-bit unsigned guest uid/gid scalars. `__MCPU_ID_NO_CHANGE` is `UINT64_MAX` and maps explicitly to the POSIX `(uid_t)-1` / `(gid_t)-1` sentinel. Other values must be representable by the selected HOST `uid_t`/`gid_t`; otherwise the wrappers return `-EOVERFLOW`. No HOST typedef is exported into the guest ABI. + +`examples/syscalls/` adds manual `chmod-file`, `fchmod-file`, `chown-file`, and `fchown-file` demonstrations. The ownership examples deliberately use the no-change sentinel so they remain safe and runnable by an ordinary user; the README explains how real ownership changes interact with HOST permissions. No other `examples/` subtree is changed. + +# LibMCPU 0.3.15 + +LibMCPU 0.3.15 extends the verified 0.3.14 pathname syscall baseline with hard and symbolic link operations: `link`, `symlink`, and `readlink`. Their syscall numbers continue the established MINIX3-inspired VFS space: `__NR_link=0x106`, `__NR_symlink=0x113`, and `__NR_readlink=0x114`. + +`link(oldpath,newpath)` and `symlink(target,linkpath)` reuse the strict NUL-terminated UTF-8 guest pathname boundary. `readlink(path,buffer,count)` writes raw target bytes into writable guest memory and returns the exact number of bytes written; it deliberately does not add a NUL terminator. The Linux/x86_64/LP64 sysdeps layer gains `sys-link.c`, `sys-symlink.c`, and `sys-readlink.c`, and configure now requires the corresponding HOST interfaces and selected wrappers. + +`examples/syscalls/` adds manual `link-file`, `symlink-file`, and `readlink-file` demonstrations. The README explains hard-link inode identity, symbolic-link target storage, the non-NUL-terminated `readlink` contract, buffer truncation semantics, and the use of the syscall return value as the byte count for a following `write`. No other `examples/` subtree is changed. + +# LibMCPU 0.3.14 + +LibMCPU 0.3.14 extends the verified 0.3.13 pathname syscall baseline with directory and access operations: `mkdir`, `access`, and `rmdir`. Their syscall numbers continue the established MINIX3-inspired VFS space: `__NR_mkdir=0x109`, `__NR_access=0x10f`, and `__NR_rmdir=0x112`. + +`mkdir(path, mode)` uses the existing strict UTF-8 pathname boundary and translates MCPU mode bits explicitly into HOST `mode_t`. `access(path, mode)` adds the stable guest constants `F_OK=0`, `X_OK=1`, `W_OK=2`, and `R_OK=4`; unsupported guest access bits are rejected with `-EINVAL` instead of being passed through to the HOST. `rmdir(path)` removes an empty directory through the selected HOST sysdeps adapter. All failures continue to return negative HOST `errno` values. + +The Linux/x86_64/LP64 sysdeps layer gains `sys-mkdir.c`, `sys-access.c`, and `sys-rmdir.c`; configure now requires all three adapters and also checks the corresponding POSIX interfaces. No new HOST structure layout or typedef is exposed to the guest ABI. + +`examples/syscalls/` adds manual `mkdir-dir`, `access-file`, and `rmdir-dir` examples with reader-oriented run scripts. The README explains requested directory modes, HOST umask interaction, `F_OK/R_OK/W_OK/X_OK`, access semantics, and the observable effects of directory creation/removal. No other `examples/` subtree is changed. + +# LibMCPU 0.3.13 + +LibMCPU 0.3.13 extends the verified 0.3.12 filesystem syscall baseline with three pathname operations: `creat`, `unlink`, and `rename`. Their syscall numbers follow the established MINIX3-inspired VFS space: `__NR_creat=0x104`, `__NR_unlink=0x107`, and `__NR_rename=0x111`. + +`creat(path, mode)` returns a LibMCPU guest descriptor and reuses the existing `open` host adapter with `O_WRONLY|O_CREAT|O_TRUNC`, preserving the already verified MCPU mode-bit translation and guest-fd namespace. `unlink(path)` and `rename(oldpath,newpath)` use the existing strict NUL-terminated UTF-8 pathname boundary and return `-host_errno` on failure. + +The Linux/x86_64/LP64 sysdeps layer gains `sys-creat.c`, `sys-unlink.c`, and `sys-rename.c`, and configure now requires these adapters for the selected HOST sysdeps hierarchy. No new host structure layout is exposed to the guest ABI. + +`examples/syscalls/` adds manual `creat-file`, `rename-file`, and `unlink-file` examples and matching reader-oriented run scripts. Its README documents the new ABI and examples in detail for later use in the LibMCPU programming book. No other `examples/` subtree is changed. + +# LibMCPU 0.3.12 + +LibMCPU 0.3.12 is a small user-facing refinement of the verified 0.3.11 filesystem-metadata syscall release. The `stat`, `fstat`, and `lstat` ABI and implementation are unchanged. + +The external assembler header `sys/asm/stat.h` now uses the `__MCPU_STAT_*` prefix consistently for the fixed 128-byte stat layout and all field offsets. The internal LibMCPU C type remains `struct __cpu_stat`; the change only separates the public assembler namespace from the private `__cpu_` C namespace. + +The manual `stat-file`, `fstat-file`, and `lstat-link` examples copy `st_size` from their `.bss` stat buffer into a small 64-bit DATA result slot at DATA offset 16. Their run scripts invoke `mcpu-run --int-data=64@16`, allowing readers to see a real metadata value returned by the syscall. The accompanying `examples/syscalls/README` explains the data flow and the expected output in detail. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b6f4bf2 --- /dev/null +++ b/README.md @@ -0,0 +1,479 @@ + +# LibMCPU + +LibMCPU is a software CPU and toolchain built on LibMPU. It provides the MCPU +instruction interpreter together with assembler, linker, loader/runtime, +inspection and archive utilities for the MELF object format. + +The current toolchain supports: + +- MELF `ET_REL` relocatable objects; +- ABI-v1 fully static `ET_EXEC` programs; +- frozen ABI-v3 startup-dynamic `ET_EXEC` and `ET_DYN` objects with + `DT_SONAME` / `DT_NEEDED`; +- historical ABI-v2 explicit-preload files in the runtime loader; +- deterministic UNIX `ar` static libraries containing MELF `ET_REL` members; +- partial relocatable linking with `mcpu-ld -r`; +- low-level MELF lifecycle executables owning `_start`, `_life` and optional + `_fini`; +- compiler-normalized HLL PROGRAM and MODEL objects with automatic CRT + selection and insertion by `mcpu-ld`; +- architectural PROGRAM/DATA/CONTROL/INPUT/OUTPUT/TRACE spaces, STACK and + optional HEAP; +- LibMPU integer/Real/Complex execution facilities. + +Successful `mcpu-as` and `mcpu-ld` invocations are quiet by default; `-v` or +`--verbose` prints the MELF output-object summary intended for development and +inspection. + +The canonical public MCPU assembler ABI headers live under +`sysroot/include/sys/asm/`. The canonical runtime-root configuration source is +`sysroot/etc/mcpu-ld.conf`, installed as `<MCPU-root>/etc/mcpu-ld.conf`. +Regression inputs are owned by `tests/fixtures/`. LibMCPU intentionally does +not contain the user-facing examples package. + +Release history belongs in [NEWS.md](NEWS.md) and `ChangeLog`; README describes +the current source tree and interfaces rather than duplicating per-release +change notes. The normative architecture and MELF references are distributed +under `doc/`. + +## Required libraries + +LibMCPU is developed and tested with: + +```text +LibMPU 1.0.25 or later +LibMPUIO 1.0.4 or later +``` + +A normal installation used by the project is: + +```text +prefix = /usr +libdir = /usr/lib64 +``` + +## Build and test + +A release archive already contains generated Autotools files: + +```sh +mkdir build +cd build +../configure \ + --prefix=/usr \ + --libdir=/usr/lib64 +make +make check +make install +``` + +For a development source tree, run `./bootstrap` before `configure` after +changing Autotools inputs. + +`make tests` is an alias for the project test suite. `make distcheck` performs +a clean distribution rebuild/install/uninstall verification. + +## Commands + +The toolchain installs these commands: + +```text +mcpu-as assembler: source -> MELF ET_REL +mcpu-ld final and partial linker +mcpu-run MELF loader/runtime and MCPU execution supervisor +mcpu-readelf read-only MELF inspection +mcpu-ar deterministic static-archive manager +mcpu-nm symbol table inspection for MELF files and archives +mcpu-objdump section, relocation and instruction inspection +mcpu-strip safe removal of non-runtime symbol metadata +mcpu-objcopy MELF-to-MELF section copy and metadata editing +``` + +Private command binaries live under: + +```text +<libdir>/mcpu/bin/ +``` + +All installed commands use one common version-reporting module: + +```text +mcpu-<tool> --version -> mcpu-<tool> (MCPU) <package-version> +mcpu-<tool> --dump-version -> <package-version> +``` + +Both forms write exactly one newline-terminated line to standard output and +exit successfully without requiring an input file or executable. Normal public +symbolic links are installed in `<bindir>`. + +## Symbol inspection + +`mcpu-nm` lists symbols from MELF `ET_REL`, `ET_EXEC`, `ET_DYN` and static +archives: + +```sh +mcpu-nm file.o +mcpu-nm program +mcpu-nm -D libfoo.so.1 +mcpu-nm libfoo.a +``` + +By default symbols are sorted by name. `-n` sorts by value, `-p` preserves +symbol-table order, `-r` reverses the selected sort, `-g` keeps only external +GLOBAL/WEAK symbols, `-u` keeps only undefined symbols, and `-A` prefixes each +output line with its input file or archive/member name. `-D` selects `.dynsym` +instead of `.symtab`. + +## Object disassembly and section inspection + +`mcpu-objdump` combines section-oriented MELF inspection with the architectural +MCPU instruction decoder: + +```sh +mcpu-objdump -h file.o +mcpu-objdump -t -r -d file.o +mcpu-objdump -T -d -r libfoo.so +mcpu-objdump -s -j .data file.o +mcpu-objdump -t libfoo.a +``` + +`mcpu-strip` removes only symbol metadata that is not required by later linking, +loading or execution. In-place operation is the default; `-o` writes a separate +result. Current `ET_REL` symbol tables and all dynamic-linking metadata are +preserved deliberately. + +```sh +mcpu-strip program +mcpu-strip -o program.stripped program +mcpu-strip libfoo.so.1 +mcpu-strip libfoo.a +``` + +`-d` disassembles executable `PROGBITS` sections. `-D` attempts disassembly of +all selected nonempty `PROGBITS` sections. `-r` displays RELA relocations and, +during disassembly, annotates the instruction containing each relocation field. +`-t` and `-T` display `.symtab` and `.dynsym` respectively, `-s` dumps raw +section contents, `-h` prints section headers, and repeatable `-j NAME` limits +section-oriented output. + +The disassembler calls the same LibMCPU decoder used by execution through +`__cpu_decode_at()`. It does not maintain a second opcode/operand parser. +Undecodable bytes are printed as `.byte` and inspection continues. + + +## Object copying and metadata editing + +`mcpu-objcopy` always produces MELF output. It is intentionally a structural +object editor rather than a ROM-image converter: + +```sh +mcpu-objcopy input output +mcpu-objcopy --dump-section=.data=data.bin input output +mcpu-objcopy --add-section=.note.build=build.txt input output +mcpu-objcopy --update-section=.note.build=new-build.txt input output +mcpu-objcopy --rename-section=.note.build=.note.release input output +mcpu-objcopy -R .note.release input output +``` + +Repeatable `-j/--only-section=NAME` performs strict filtering, but the operation +is rejected if it would discard runtime-visible, dynamic-linking or link-critical +sections. Added sections are always non-allocated `SHT_PROGBITS` metadata, and +update/rename are restricted to safe non-allocated `PROGBITS`. There are no +`binary`, Intel HEX, S-record, VMA/LMA or section-flag conversion modes. + +Deterministic MCPU archives are edited member-by-member. `--dump-section` is +accepted for a single MELF input and is rejected for archive input because one +`NAME=FILE` destination would be ambiguous across members. + +## Manual pages + +The release installs English section-1 manual pages for every command and a +section-7 overview page: + +```text +mcpu-as(1) mcpu-ld(1) mcpu-run(1) +mcpu-readelf(1) mcpu-ar(1) mcpu-nm(1) +mcpu-objdump(1) mcpu-strip(1) mcpu-objcopy(1) +libmcpu(7) +``` + +Synchronized Russian pages are installed below the locale-specific man tree: + +```text +<mandir>/ru/man1/ +<mandir>/ru/man7/ +``` + +For a conventional `/usr` installation the English pages live under +`/usr/share/man/man1` and `/usr/share/man/man7`; the Russian pages live under +`/usr/share/man/ru/man1` and `/usr/share/man/ru/man7`. The manual pages are +written from the current command interfaces and complement, rather than +replace, the maintained architecture/MELF references. + +Manual-page sources intentionally do not embed the package release number in +the `.TH` source field. The installed commands are the authoritative source of +the package version through `--version` and `--dump-version`. This keeps man +pages tied to an interface contract instead of requiring a mechanical version +edit on every release. + +## HLL complete-program contract and CRT + +Final executable linking recognizes two compiler-normalized HLL complete-program +forms directly from the ordinary input `ET_REL` objects; no `--program` or +`--model` mode option exists. The defining object must use strong +`GLOBAL/FUNC` symbols and keep the complete-program definitions in one object. +This is an object-level contract: if a source language makes `fini` optional, +its compiler must synthesize an empty strong `fini` during normalization: + +```text +PROGRAM: main + fini +MODEL: init + life + fini +``` + +The binary HLL signatures are: + +```text +PROGRAM + int32 main(int64 argc, PTR64 argv) + void fini(void) + +MODEL + int32 init(int64 argc, PTR64 argv) + int32 life(void) + void fini(void) +``` + +For a final `ET_EXEC` link, `mcpu-ld` classifies the explicit input objects +before library extraction and automatically inserts CRT objects from the active +MCPU root: + +```text +PROGRAM: crt0.o crti.o <user objects> crtn.o +MODEL: crt1.o crti.o <user objects> crtn.o +``` + +The CRT objects are required at `<MCPU-root>/lib/mcpu/`. A missing or invalid +required CRT object is a fatal link error; the linker does not search a fallback +root and does not degrade an HLL link into low-level lifecycle linking. + +A low-level executable continues to define `_start` and `_life` itself, with +optional `_fini`, and receives no automatic HLL CRT. Low-level lifecycle +ownership and HLL complete-program roots cannot be mixed in one final link. + +The loader constructs one persistent `McpuStartup` v1 area before `_start`. +The CRT validates it, preserves the loader-owned `argv`, converts `argc` to the +target signed `int64` HLL ABI, calls the MCPU Libc runtime hooks, and invokes the +HLL functions through the ordinary CALL ABI. HLL functions return normally +with `RET`; lifecycle `END`/`EXIT` behavior belongs to CRT. + +For MODEL, `life()` returns `INT32_MIN` to continue through exactly one LIFE +`END`, zero for normal model completion, or a positive status for an error exit. +Other negative values are reserved by HLL Runtime ABI v1. Graceful finalization +orders user `fini()`, `__mcpu_fini_array()` and `__mcpu_libc_fini()` before the +final FINI `END`; failures before runtime startup skip those HLL finalizers. + +## Basic object and executable workflow + +Assemble a source file: + +```sh +mcpu-as -o main.o main.s +``` + +Build a fully static ABI-v1 executable: + +```sh +mcpu-ld --stack-size=64K --no-heap \ + -o program main.o helper.o +mcpu-run program +``` + +The executable owns its lifecycle entries, STACK descriptor and optional HEAP +policy. `mcpu-run` does not replace executable STACK/HEAP values. + +## Partial relocatable linking + +`mcpu-ld -r` combines several relocatable inputs into one new MELF `ET_REL`: + +```sh +mcpu-ld -r -o combined.o a.o b.o c.o +``` + +The result is still relocatable. Section contributions are merged with their +alignment preserved, symbols are rebased to the merged sections and RELA +records remain available for a later link. Strong undefined symbols may +remain unresolved: + +```sh +mcpu-ld -r -o library-part.o a.o b.o +mcpu-ld --stack-size=64K -o program main.o library-part.o final-provider.o +``` + +Partial linking does not create lifecycle entries, STACK/HEAP descriptors, +SONAME/NEEDED metadata or a runtime image. `-r` uses only static archives for +`-lNAME`. + +## Static archives + +Create a deterministic archive of independent MELF `ET_REL` members: + +```sh +mcpu-ar rcs libsimple.a add-one.o add-two.o value.o +mcpu-ar t libsimple.a +mcpu-ar tv libsimple.a +``` + +`mcpu-ar` supports the principal `r`, `q`, `d`, `t`, `p`, `x` and `s` +operations. Archives use the ordinary UNIX `ar` container format and include +a real System-V/GNU-style symbol index when requested. + +During a normal link, archive members are extracted only when they define a +currently unresolved strong GLOBAL symbol. An unresolved WEAK reference does +not itself pull a member from an archive. + +For an ABI-v1 link, `-lfoo` searches only `libfoo.a`. For ABI-v3 dynamic or +shared output, search is directory-major and each directory is tried as: + +```text +libfoo.so +libfoo.a +``` + +Thus `.so` wins over `.a` in the same directory, while an archive in an earlier +`-L` directory wins over a shared object in a later directory. + +`mcpu-ld --static` forces every `-lfoo` lookup to consider only `libfoo.a` +without changing the selected output mode. For example, +`--dynamic --static` still produces an ABI-v3 `ET_EXEC`, and +`--shared --static` still produces an ABI-v3 `ET_DYN`; selected archive members +are incorporated into the output and therefore add no `DT_NEEDED`. The option +is also accepted with the already-static ABI-v1 and `-r` modes. + +## ABI-v3 shared libraries + +Create an ABI-v3 shared object with a mandatory SONAME: + +```sh +mcpu-ld --shared --soname=libfoo.so.1 \ + -o libfoo.so.1.0.0 foo.o +ln -s libfoo.so.1.0.0 libfoo.so.1 +ln -s libfoo.so.1 libfoo.so +``` + +Link a startup-dynamic executable: + +```sh +mcpu-ld --dynamic --stack-size=64K --no-heap \ + -L. -lfoo -o program main.o +``` + +The executable records the provider SONAME in `DT_NEEDED`; it does not record +the development link name or physical versioned filename. `mcpu-run` resolves +NEEDED dependencies recursively before `_start` and performs all retained +relocations before guest execution. There is no PLT/GOT, lazy binding, +`dlopen()` or runtime `dlsym()` API. + +## Library search and configuration + +`mcpu-ld` and `mcpu-run` share the relocatable MCPU system-root policy. In +increasing priority, the linker configuration chain is: + +```text +<runtime-root>/etc/mcpu-ld.conf +/etc/mcpu/mcpu-ld.conf +$HOME/.mcpu/etc/mcpu-ld.conf +``` + +Only the runtime-root configuration file is installed by LibMCPU. + +The main system-library value is: + +```text +MCPU_LD_SYSTEM_LIBRARY_PATH = <path-list>; +``` + +Useful common options include: + +```text +--no-config +--sys-root=PATH +--dump-config +--print-search-dirs +``` + +`mcpu-ld -L` directories precede the configured system library path. +`mcpu-run --library-path` similarly precedes the effective system path. + +## Explicit preload + +`mcpu-run --ldpreload=` remains the explicit startup interposition mechanism. +The environment form is: + +```sh +MCPU_LD_PRELOAD=liba.so:libb.so mcpu-run program +``` + +Environment preloads are placed before command-line preloads. For equal +binding strength, explicit preload priority is rightmost-higher-priority, so a +command-line preload naturally overrides an environment preload. GLOBAL +definitions outrank WEAK definitions. + +Normal ABI-v3 dependency scope is deterministic: the main executable precedes +ordinary dependencies; the `DT_NEEDED` graph is breadth-first, with direct +dependencies before transitive dependencies and siblings in recorded order. +Explicit preloads have higher equal-strength scope than the main executable. + +## Inspecting MELF files + +Examples: + +```sh +mcpu-readelf -h file.o +mcpu-readelf -S file.o +mcpu-readelf -s file.o +mcpu-readelf -r file.o +mcpu-readelf -l program +mcpu-readelf -d program +mcpu-readelf -A program +mcpu-readelf -a program +``` + +Section headers are intentionally printed in a wide one-section-per-line +format suitable for reading, `grep`, `awk` and diffs. + +## Documentation + +The shipped Russian PDF reference manuals are release-number-independent architectural documents. `doc/libmcpu-bytecode-architecture-ru.pdf` includes the signed HLL Runtime ABI (mcpu-ld + CRT + MCPU Libc) and permanent appendices for assembler directives, all OPCODE16 values, PSW selectors and RCONST. `doc/libmcpu-melf-abi-ru.pdf` documents the MELF container and linker/runtime contracts. Package release history belongs to `NEWS.md` and `ChangeLog`. + + +Maintained manuals: + +```text +doc/libmcpu-bytecode-architecture-ru.pdf +doc/libmcpu-melf-abi-ru.pdf +``` + +Editable XeLaTeX sources are under `doc/src/`; the signed HLL/CRT contract +source is `doc/src/libmcpu-hll-crt-contract-ru.tex`. Rebuild the maintained PDF +manuals with: + +```sh +cd doc/src +./build-docs.sh +``` + +The MELF manual is the detailed reference for object/executable layout, +relocations, lifecycle, ABI-v1/ABI-v2/ABI-v3 compatibility, dynamic dependency +metadata, static archives and linker/runtime contracts. + +## Examples + +Manual and book examples are intentionally distributed as a separate package. +LibMCPU does not contain an `examples/` tree. Regression inputs needed by +`make check` are self-contained under `tests/fixtures/` and are not intended as +the user-facing examples distribution. + +## License + +See `COPYING`. diff --git a/acsite.m4 b/acsite.m4 new file mode 100644 index 0000000..8de5c55 --- /dev/null +++ b/acsite.m4 @@ -0,0 +1,417 @@ +dnl ============================================================ +dnl Force configure to run under /bin/bash +dnl ============================================================ +m4_define([AC_MCPU_REQUIRE_BASH], [dnl +m4_divert_push([M4SH-SANITIZE])dnl + +if test ! -x /bin/bash; then + echo "configure: error: /bin/bash is required" >&2 + exit 1 +fi + +CONFIG_SHELL=/bin/bash +export CONFIG_SHELL + +m4_divert_pop([M4SH-SANITIZE])dnl +])dnl + + +dnl ============================================================ +dnl Support for Configuration Headers +dnl +dnl configure.ac: +dnl AC_MCPU_HEADLINE(<short-name>, <long-name>, +dnl <vers-var>, <copyright>) +dnl ============================================================ +m4_define([AC_MCPU_HEADLINE], [dnl +m4_divert_push([M4SH-INIT])dnl +{ + if test ".`echo dummy [$]@ | grep help`" = .; then + + ####### Use a `Quadrigaph'. '@<:@' gives you [ and '@:>@' gives you ] : + TB=`echo -n -e '\033@<:@1m'` + TN=`echo -n -e '\033@<:@0m'` + + ####### Get the short product version from AC_INIT(). + $3="AC_PACKAGE_VERSION" + AC_SUBST($3) + + ####### Print configuration headline. + echo "Configuring:" + echo "" + echo "${TB}$1${TN} ($2), Version ${TB}${$3}${TN}" + echo "$4" + echo "" + fi +} +m4_divert_pop([M4SH-INIT])dnl +])dnl + + +dnl ============================================================ +dnl Display Configuration Headers +dnl +dnl configure.ac: +dnl AC_MSG_CFG_PART(<text>) +dnl ============================================================ +AC_DEFUN([AC_MSG_CFG_PART],[dnl + AC_MSG_RESULT() + AC_MSG_RESULT([${TB}$1:${TN}]) +])dnl + + +dnl ============================================================ +dnl GNU GCC is required by LibMCPU. +dnl ============================================================ +AC_DEFUN([MCPU_REQUIRE_GCC], [dnl + AC_MSG_CHECKING([whether $CC is GNU GCC]) + AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([[ +#if !defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER) +#error LibMCPU requires GNU GCC +#endif +int main( void ) { return 0; } + ]]) + ], [ + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + AC_MSG_ERROR([LibMCPU requires GNU GCC]) + ]) +]) + + +dnl ============================================================ +dnl Provide the Autoconf state required by cpu/sysdeps/*/ +dnl preconfigure.ac fragments. The generated preconfigure files +dnl are sourced by the top-level configure after AC_PROG_CC. +dnl ============================================================ +define([MCPU_PROVIDES], [dnl +AC_PROVIDE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_PROVIDE([AC_CONFIG_SUBDIRS])dnl +AC_PROVIDE([_AS_ECHO_N_PREPARE])dnl +AC_PROVIDE([_AS_ECHO_PREPARE])dnl +AC_PROVIDE([_AS_CR_PREPARE])dnl +AC_PROVIDE([_AS_TR_SH_PREPARE])dnl +AC_PROVIDE([_AS_VAR_ARITH_PREPARE])dnl +AC_PROVIDE([AC_PROG_INSTALL])dnl +AC_PROVIDE([AC_PROG_CC])dnl +AC_PROVIDE([AC_PROG_CPP])dnl +AC_PROVIDE([_AS_PATH_SEPARATOR_PREPARE])dnl +AC_PROVIDE([_AS_TEST_PREPARE])dnl +AC_PROVIDE([_AS_BASENAME_PREPARE])dnl +AC_PROVIDE([_AS_ME_PREPARE])dnl +AC_PROVIDE([_AS_LINENO_PREPARE])dnl +AC_PROVIDE([AS_SHELL_FN_as_fn_set_status])dnl +AC_PROVIDE([AS_SHELL_FN_as_fn_exit])dnl +AC_PROVIDE([AS_SHELL_FN_as_fn_arith])dnl +AC_PROVIDE([AS_SHELL_FN_ac_fn_c_try_compile])dnl +define([AS_MESSAGE_LOG_FD],5)dnl +define([AS_MESSAGE_FD],6)dnl +m4_wrap([m4_divert_pop([BODY])[]]) +m4_divert_push([BODY])[]dnl +# This file is generated from preconfigure.ac by Autoconf. DO NOT EDIT! +define([_AC_LANG], [C])dnl +])dnl + + +dnl ============================================================ +dnl Find and source cpu/sysdeps/*/preconfigure fragments. +dnl +dnl MCPU_PRECONFIGURE([$srcdir], [for cpu/sysdeps]) +dnl ============================================================ +AC_DEFUN([MCPU_PRECONFIGURE], [dnl +if frags=`ls -d $1/cpu/sysdeps/*/preconfigure 2> /dev/null` +then + AC_MSG_CHECKING([$2 preconfigure fragments]) + mcpu_frag_names= + for frag in $frags; do + name=`echo "$frag" | sed 's@/[[^/]]*[$]@@;s@^.*/@@'` + mcpu_frag_names="$mcpu_frag_names $name" + done + AC_MSG_RESULT([$mcpu_frag_names]) + for frag in $frags; do + . "$frag" + done +fi +]) + + +dnl ============================================================ +dnl Build the ordered cpu/sysdeps search path for the configured +dnl HOST. The algorithm follows the historical LibMPU sysdeps +dnl model: architecture preconfigure fragments may refine MACHINE +dnl (including the ABI/data model), existing OS/CPU directories are +dnl discovered from the most specific to the most generic, and +dnl Implies/Implies-before/Implies-after expand the inheritance +dnl chain before cpu/sysdeps/generic is appended. +dnl +dnl Substitutions: +dnl base_machine canonical base host architecture +dnl submachine optional --with-cpu suffix +dnl MCPU_HOST_MACHINE refined machine[/data-model] +dnl MCPU_HOST_DATA_MODEL ILP32/LP64/... selected by preconfigure +dnl sysnames ordered source lookup directories +dnl ============================================================ +AC_DEFUN([MCPU_SYSDEP_DIR_NAMES], [dnl +case "$host_os" in + linux*) ;; + gnu*) host_os=`echo "$host_os" | sed -e 's/gnu/gnu-gnu/'` ;; +esac + +config_machine=$host_cpu +config_vendor=$host_vendor +config_os=$host_os + +test "$config_vendor" = unknown && config_vendor= +config_os="`echo "$config_os" | sed 's/^unknown-//'`" + +machine=$config_machine +vendor=$config_vendor +os=$config_os +base_os='' +base_machine= +MCPU_HOST_DATA_MODEL= + +submachine= +AC_ARG_WITH([cpu], + AS_HELP_STRING([--with-cpu=CPU], [select host CPU variant for sysdeps]), + [dnl + case "$withval" in + yes | '') AC_MSG_ERROR([--with-cpu requires an argument]) ;; + no) ;; + *) submachine="$withval" ;; + esac + ]) + +AC_ARG_ENABLE([debug-configure], + AS_HELP_STRING([--enable-debug-configure], + [show detailed cpu/sysdeps directory selection @<:@default=no@:>@]), + [enable_debug_configure=$enableval], + [enable_debug_configure=no]) +case "$enable_debug_configure" in + no) enable_debug_configure='' ;; + *) ;; +esac + +MCPU_PRECONFIGURE([$srcdir], [for cpu/sysdeps]) + +test -n "$base_machine" || base_machine=$machine +AC_SUBST([base_machine]) +AC_SUBST([submachine]) + +sysdep_dir=$srcdir/cpu/sysdeps +AC_MSG_CHECKING([cpu sysdep dirs]) +os="`echo "$os" | sed 's/\([[0-9A-Z]]\)[[v_]]\([[0-9A-Z]]\)/\1.\2/g'`" + +test "x$base_os" != x || case "$os" in + linux*) base_os=unix/sysv ;; +esac + +# OS candidates: linux-gnu, linux, etc. +tail=$os +ostry=$os +while o=`echo "$tail" | sed 's/\.[[^.]]*$//'`; test "$o" != "$tail"; do + ostry="$ostry /$o" + tail=$o +done +o=`echo "$tail" | sed 's/[[0-9]]*$//'` +if test "$o" != "$tail"; then + ostry="$ostry /$o" +fi +o=`echo "$tail" | sed 's/-.*$//'` +if test "$o" != "$tail"; then + ostry="$ostry /$o" +fi + +# Base OS hierarchy: unix/sysv -> unix/sysv, unix. +base= +tail=$base_os +while b=`echo "$tail" | sed 's@^\(.*\)/\([[^/]]*\)[$]@& \1@'`; test -n "$b"; do + set $b + base="$base /[$]1" + tail="[$]2" +done + +# Machine hierarchy: x86_64/64 -> x86_64/64, x86_64. +mach= +tail=$machine${submachine:+/$submachine} +while m=`echo "$tail" | sed 's@^\(.*\)/\([[^/]]*\)[$]@& \1@'`; test -n "$m"; do + set $m + mach="$mach /[$]1" + tail="[$]2" +done + +sysnames= +os_used= +machine_used= +submachine_used= +for b in $base ''; do + for m0 in $mach ''; do + for v in /$vendor ''; do + test "$v" = / && continue + for o in /$ostry ''; do + test "$o" = / && continue + for m in $mach ''; do + try_suffix="$m0$b$v$o$m" + if test -n "$try_suffix"; then + try="cpu/sysdeps$try_suffix" + test -n "$enable_debug_configure" && echo "configure: sysdeps try: $try" >&2 + if test -d "$srcdir/$try"; then + case " $sysnames " in + *" $try "*) ;; + *) sysnames="$sysnames $try" ;; + esac + { test -n "$o" || test -n "$b"; } && os_used=t + { test -n "$m" || test -n "$m0"; } && machine_used=t + case x${m0:-$m} in + x*/$submachine) test -n "$submachine" && submachine_used=t ;; + esac + fi + fi + done + done + done + done +done + +# The sysdeps layer is deliberately narrower than the generic C build. +# If LibMPU itself was not usable AC_CHECK_LIBMPU has already stopped +# configure. Here we additionally require a known LibMCPU host OS/CPU +# hierarchy; generic alone never constitutes a supported HOST. +if test -z "$os_used" && test "$os" != none; then + AC_MSG_ERROR([Not supported HOST $host: no cpu/sysdeps operating-system hierarchy for $os]) +fi +if test -z "$machine_used" && test "$machine" != none; then + AC_MSG_ERROR([Not supported HOST $host: no cpu/sysdeps machine hierarchy for $machine]) +fi +if test -n "$submachine" && test -z "$submachine_used"; then + AC_MSG_ERROR([Not supported HOST $host: CPU variant $submachine is not represented in cpu/sysdeps]) +fi + +# Expand existing names through parent directories and Implies files. +set $sysnames +names= +while test [$]# -gt 0; do + name=[$]1 + shift + + case " $names " in + *" $name "*) continue ;; + esac + + name_base=`echo "$name" | sed -e 's@\(.*cpu/sysdeps\)/.*@\1@'` + + Implies= + Implies_before= + Implies_after= + for implies_file in Implies Implies-before Implies-after; do + implies_type=`echo "$implies_file" | sed 's/-/_/'` + eval ${implies_type}= + if test -f "$srcdir/$name/$implies_file"; then + implied_candidate="`sed 's/#.*$//' < "$srcdir/$name/$implies_file"`" + for x in $implied_candidate; do + found=no + try="$name_base/$x" + test -n "$enable_debug_configure" && echo "configure: $name/$implies_file -> $try" >&2 + if test -d "$srcdir/$try"; then + eval "${implies_type}=\"\$${implies_type} \$try\"" + found=yes + fi + if test "$found" = no; then + AC_MSG_WARN([$name/$implies_file specifies nonexistent $x]) + fi + done + fi + done + + names="$names $name" + +changequote(,)dnl + parent="`echo "$name" | sed -n -e 's=/[^/]*$==' -e '/cpu\/sysdeps$/q' -e p`" +changequote([,])dnl + + test -n "$enable_debug_configure" && + echo "configure: sysdeps select: $name; implies='$Implies'; parent='$parent'; before='$Implies_before'; after='$Implies_after'" >&2 + + sysnames="`echo $Implies [$]* $Implies_before $parent $Implies_after`" + test -n "$sysnames" && set $sysnames +done + +default_sysnames="cpu/sysdeps/generic" +sysnames="`echo $names $default_sysnames`" +MCPU_HOST_MACHINE=$machine + +AC_SUBST([MCPU_HOST_MACHINE]) +AC_SUBST([MCPU_HOST_DATA_MODEL]) +AC_SUBST([sysnames]) + +AC_MSG_RESULT([$sysnames]) +]) + +AC_DEFUN([AC_LIBMCPU_LT_VERSION_INFO], +[AC_MSG_CHECKING(for MCPU Library Version Information) +# +# Made LT_VERSION_INFO (may be used libtool-next-version from libtool-2.6.2): +# +# LT_VERSION_INFO="current:revision:age" +# +info="$1" + +current=`echo ${info} | cut -f1 -d':'` +revision=`echo ${info} | cut -f2 -d':'` +age=`echo ${info} | cut -f3 -d':'` + +ac_cv_libmcpu_lt_release="${current}.${revision}" +ac_cv_libmcpu_lt_current="${current}" +ac_cv_libmcpu_lt_revision="${revision}" +ac_cv_libmcpu_lt_age="${age}" + +ac_cv_libmcpu_lt_version_info="${current}:${revision}:${age}" + +AC_SUBST(LIBMCPU_LT_RELEASE, $ac_cv_libmcpu_lt_release) +AC_SUBST(LIBMCPU_LT_VERSION_INFO, $ac_cv_libmcpu_lt_version_info) +AC_SUBST(LIBMCPU_LT_CURRENT, $ac_cv_libmcpu_lt_current) +AC_SUBST(LIBMCPU_LT_REVISION, $ac_cv_libmcpu_lt_revision) +AC_SUBST(LIBMCPU_LT_AGE, $ac_cv_libmcpu_lt_age) + +AC_MSG_RESULT($ac_cv_libmcpu_lt_version_info) +if test "$ac_cv_libmcpu_lt_version_info" = ""; then +AC_MSG_ERROR([ +******** ${TB}The MCPU Library Version Information does not declared.${TN}]) +fi +]) + + +dnl ============================================================ +dnl Test if LD_LIBRARY_PATH contains the notation for the +dnl current directory since this would lead to problems +dnl installing/building kxlibc . +dnl +dnl LD_LIBRARY_PATH contains the current directory if one +dnl of the following is true: +dnl - one of the terminals (":" and ";") is the first or +dnl last sign; +dnl - two terminals occur directly after each other; +dnl - the path contains an element with a dot in it . +dnl ============================================================ +AC_DEFUN([AC_LD_LIBRARY_PATH], +[AC_MSG_CHECKING(for LD_LIBRARY_PATH does not contain current directory) +changequote(,)dnl +case ${LD_LIBRARY_PATH} in + [:\;]* | *[:\;] | *[:\;][:\;]* | *[:\;]. | .[:\;]*| . | *[:\;].[:\;]* ) + ld_library_path_setting="contains current directory" + ;; + *) + ld_library_path_setting="ok" + ;; +esac +changequote([,])dnl +AC_MSG_RESULT($ld_library_path_setting) +if test "$ld_library_path_setting" != "ok"; then +AC_MSG_ERROR([ +******** ${TB}LD_LIBRARY_PATH shouldn't contain the current directory when${TN} +******** ${TB}building MCPU Library. Please change the environment variable${TN} +******** ${TB}and run configure again.${TN}]) +fi +]) diff --git a/auto-clean b/auto-clean new file mode 100755 index 0000000..346660a --- /dev/null +++ b/auto-clean @@ -0,0 +1,42 @@ +#!/bin/bash + +CWD=`pwd` + +program=`basename $0` + +usage() { + cat << EOF + +Usage: $program [options] + +Options: + -h,--help Display this message. + +EOF +} + +if [ -f "${CWD}/Makefile" ] ; then + make distclean +fi + +gitignore='.gitignore' + +if [ -f "$gitignore" ] ; then + while read ln; do + line=`echo "${ln}" | sed 's,^[ \t],,' | sed 's,[ \t]$,,'` + if [ "x$line" != "x" -a "${line:0:1}" != "#" ] ; then + if `echo "${line}" | grep -q '\*~$'` ; then + find "`dirname "${line}"`" -type f -iname '*~' -print0 | while IFS= read -r -d '' file ; do + rm -f "$file" + done + elif `echo "${line}" | grep -q '\*'` ; then + find "`dirname "${line}"`" -type f -iname "`basename "${line}"`" -print0 | while IFS= read -r -d '' file ; do + rm -f "$file" + done + else + if [ -d "${line}" ] ; then rm -rf "${line}" ; fi + if [ -f "${line}" ] ; then rm -f "${line}" ; fi + fi + fi + done < ${CWD}/${gitignore} +fi diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..9063c42 --- /dev/null +++ b/bootstrap @@ -0,0 +1,128 @@ +#!/bin/bash + +CWD=`pwd` +program=`basename $0` + +usage() { + cat << EOF_USAGE + +Usage: $program [options] + +Options: + -h,--help Display this message. + -d,--target-dest-dir=DIR The target ROOTFS directory + [default: DIR=/]. + +The script regenerates files that do not need to be stored in Git: + libtool helper files and m4 macros using libtoolize; + aclocal.m4 and external/vendor m4 macros using aclocal; + config.h.in using autoheader; + Makefile.in and helper files using automake; + configure using autoconf. + +EOF_USAGE +} + +TARGET_DEST_DIR=/ +ACDIR=usr/share/aclocal +INCDIR=usr/include +SYSTEM_ACDIR= +SYSTEM_INCDIR= + +while [ 0 ] ; do + if [ "$1" = "-h" -o "$1" = "--help" ] ; then + usage + exit 0 + elif [ "$1" = "-d" -o "$1" = "--target-dest-dir" ] ; then + if [ "$2" = "" ] ; then + echo -e "\n${program}: ERROR: --target-dest-dir is not specified.\n" + usage + exit 1 + fi + TARGET_DEST_DIR="$2" + shift 2 + elif [[ $1 == --target-dest-dir=* ]] ; then + TARGET_DEST_DIR="`echo $1 | cut -f2 -d'='`" + shift 1 + else + if [ "$1" != "" ] ; then + echo -e "\n${program}: ERROR: Unknown argument: $1.\n" + usage + exit 1 + fi + break + fi +done + +if [ ! -d "${TARGET_DEST_DIR}" ] ; then + echo -e "\n${program}: ERROR: --target-dest-dir is not a directory.\n" + usage + exit 1 +fi + +# Absolute path: +if [ "${TARGET_DEST_DIR:0:1}" != "/" ] ; then + TARGET_DEST_DIR=${CWD}/${TARGET_DEST_DIR} +fi + +# Remove last '/' char except for the root itself: +if [ "${TARGET_DEST_DIR}" != "/" -a "${TARGET_DEST_DIR: -1}" = "/" ] ; then + len=${#TARGET_DEST_DIR} + let "len = len - 1" + tmp="${TARGET_DEST_DIR:0:$len}" + TARGET_DEST_DIR=${tmp} +fi + +if [ "${TARGET_DEST_DIR}" = "/" ] ; then + SYSTEM_ACDIR=/${ACDIR} + SYSTEM_INCDIR=/${INCDIR} +else + SYSTEM_ACDIR="${TARGET_DEST_DIR}/${ACDIR}" + SYSTEM_INCDIR="${TARGET_DEST_DIR}/${INCDIR}" +fi + +################################################################ +# HOST sysdeps preconfigure fragments: +# +for fragment in cpu/sysdeps/*/preconfigure.ac ; do + if [ -f "${fragment}" ] ; then + dir=`dirname "${fragment}"` + ( cd "${dir}" && ./configure-fragment ) || exit 1 + fi +done +# +# End of HOST sysdeps preconfigure fragments. +################################################################ + +################################################################ +# Libtool: +# +libtoolize --force --copy || exit 1 +# +# Используется если в configure.ac вызывается LT_INIT. +# Patch необходим по тому, что и lib-ld.m4 и libtool.m4 +# используют функцию AC_ARG_WITH([gnu-ld], ... +# а это приводит к тому, что ./configure --help дважды +# выдает подсказку --with-gnu-ld : +# +patch -p0 < $CWD/config/patches/libtool.m4.patch +patch -p0 < $CWD/config/patches/libtool.m4-shared.patch +# +# End of Libtool. +################################################################ + + +aclocal --install -I m4 --force --system-acdir=${SYSTEM_ACDIR} || exit 1 +autoheader --include=${SYSTEM_INCDIR} || exit 1 +automake --gnu --add-missing --copy --force-missing || exit 1 +autoconf --force || exit 1 + + +################################################################ +# Remove cache and backup files: +# +rm -rf autom4te.cache *~ cpu/*~ utils/*~ tests/*~ doc/*~ +find cpu/sysdeps -type f -name '*~' -delete 2>/dev/null || true +# +# End of Cleanup. +################################################################ diff --git a/config/README.libtool b/config/README.libtool new file mode 100644 index 0000000..919d0f7 --- /dev/null +++ b/config/README.libtool @@ -0,0 +1,78 @@ + +/******************************************************************* + + MCPU Library Versioning: + ======================= + + VERSION (major.minor.micro): + --------------------------- + - major - version changes when the library’s API was modified, + - minor - version changes when the library’s functionality was + modified in a backwards-compatible manner, + - micro - version changes when backwards-compatible bug fixes + or code changes were made. + NOTE: + ---- + The VERSION value in this file should be equal to version + which declared in the configure.ac file on the following + function call: + + AC_INIT([MCPU Library], [1.0.0], ... + + + LT_VERSION_INFO (-version-info current:revision:age): + ---------------------------------------------------- + - current - version changes when the library’s interface was + modified (in this case the revision value is + reset to zero), + - revision - version changes when the library’s source was + changed, + - age - version increases when one new function was added + and decreases one old function was removed. + + + EXAMPLE 1: + --------- + 1. Begin with the previous version information: 0:0:0 + 2. 0:0:0 becomes 0:1:0 (the library’s source was changed) + 3. 0:1:0 becomes 1:0:0 (the library’s interface was modified) + 4. 1:0:0 becomes 1:0:1 (one new function was added) + 5. 1:0:1 becomes 1:0:0 (one old function was removed) + + EXAMPLE 2: + --------- + Let’s modify our example just a little to say that we’ve added + a new library interface function but haven’t removed anything. + Start again with the original version information of 0:0:0 and + follow the algorithm: + + 1. Begin with the previous version information: 0:0:0 + 2. 0:0:0 becomes 0:1:0 (the library’s source was changed) + 3. 0:1:0 becomes 1:0:0 (the library’s interface was modified) + 4. 1:0:0 becomes 1:0:1 (one new function was added) + 5. Not applicable (nothing was removed) + + This time, we end up with a Libtool version string of 1:0:1, + but the resulting Linux or Solaris shared-library filename is + libname.so.0.1.0. Consider for a moment what it means, in the + face of major, minor, and patch-level values, to have a nonzero + age value in the Libtool version string. An age value of one + (as in this case) means that we are effectively still supporting + a Linux major value of zero, because this new version of the + library is 100-percent backward compatible with the previous + version. The minor value in the shared-library filename has been + incremented from zero to one to indicate that this is, in fact, + an updated version of the soname, libname.so.0. The patch-level + value remains at zero because this value indicates a bug fix to + a particular minor revision of an soname. + + + The function: + + AC_LIBMCPU_LT_VERSION_INFO([1:0:0]) + + defined in acsite.m4. + This function produces LIBMCPU_LT_VERSION_INFO value for LDFLAGS + -version-info. The LT_VERSION_INFO is not equal PACKAGE_VERSION + + *******************************************************************/ diff --git a/config/patches/libtool.m4-shared.patch b/config/patches/libtool.m4-shared.patch new file mode 100644 index 0000000..9389d02 --- /dev/null +++ b/config/patches/libtool.m4-shared.patch @@ -0,0 +1,20 @@ +--- m4-orig/libtool.m4 2026-06-07 17:43:33.877209247 +0300 ++++ m4/libtool.m4 2026-06-07 17:45:22.508205160 +0300 +@@ -4998,7 +4998,7 @@ + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) +- _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" ++ _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC -DSHARED],[m4_if([$1],[CXX],[ -DPIC -DSHARED],[])])" + ;; + esac + +@@ -5013,7 +5013,7 @@ + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], +- [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], ++ [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC -DSHARED],[m4_if([$1],[CXX],[ -DPIC -DSHARED],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; diff --git a/config/patches/libtool.m4.patch b/config/patches/libtool.m4.patch new file mode 100644 index 0000000..eba3758 --- /dev/null +++ b/config/patches/libtool.m4.patch @@ -0,0 +1,15 @@ +--- m4-orig/libtool.m4 2026-06-07 17:38:21.701220990 +0300 ++++ m4/libtool.m4 2026-06-07 17:39:59.924217295 +0300 +@@ -3374,12 +3374,6 @@ + m4_require([_LT_DECL_EGREP])dnl + m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +-AC_ARG_WITH([gnu-ld], +- [AS_HELP_STRING([--with-gnu-ld], +- [assume the C compiler uses GNU ld @<:@default=no@:>@])], +- [test no = "$withval" || with_gnu_ld=yes], +- [with_gnu_ld=no])dnl +- + ac_prog=ld + if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..5eb8c0f --- /dev/null +++ b/configure.ac @@ -0,0 +1,273 @@ + +AC_PREREQ([2.71]) + +AC_INIT([MCPU Library],[0.4.0],[Andrey V.Kosteltsev <support@radix-linux.su>],[libmcpu],[https://radix-linux.su]) + +AC_MCPU_REQUIRE_BASH + +AC_MCPU_HEADLINE([libmcpu], [MCPU Library], [LIBMCPU_VERSION],dnl +[Copyright (C) 2026 Andrey V.Kosteltsev])dnl + +AC_MSG_CFG_PART(Getting the canonical system type) +AC_CANONICAL_HOST +AC_CANONICAL_TARGET +AC_USE_SYSTEM_EXTENSIONS + +dnl Должна вызываться строго после AC_CANONICAL_TARGET . +AC_ARG_PROGRAM + +AC_MSG_CFG_PART(Init Automake environment) +AC_CONFIG_SRCDIR([acsite.m4]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign dist-xz no-dist-gzip subdir-objects]) + +AC_MSG_CFG_PART(Test for GNU Compiler Collection) +AC_USE_SYSTEM_EXTENSIONS +AC_PROG_CC +AC_PROG_LN_S +MCPU_REQUIRE_GCC + +AC_MSG_CFG_PART(Test for GNU libtool) +LT_PREREQ([2.4.6]) +LT_INIT +AC_SUBST([LIBTOOL_DEPS]) + +AC_MSG_CFG_PART(Test for libtool VERSION Information) +AC_LIBMCPU_LT_VERSION_INFO([1:0:0]) + +AC_MSG_CFG_PART(Test for LD_LIBRARY_PATH Environment Variable) +AC_LD_LIBRARY_PATH + + +AC_MSG_CFG_PART(Test for required POSIX interfaces) +AC_CHECK_HEADERS( + [sys/mman.h sys/stat.h fcntl.h unistd.h poll.h time.h], + [], + [AC_MSG_ERROR([LibMCPU requires POSIX mmap headers])] +) +AC_CHECK_FUNCS( + [mmap mprotect munmap sysconf open openat close dup read write lseek link linkat unlink unlinkat mkdir mkdirat chmod fchmodat chown fchownat access faccessat rename renameat rmdir symlink symlinkat readlink readlinkat fstatat fcntl pipe2 ppoll fsync truncate ftruncate fchmod fchown clock_gettime clock_getres clock_nanosleep], + [], + [AC_MSG_ERROR([LibMCPU requires mmap/mprotect/munmap/sysconf])] +) + +AC_MSG_CFG_PART(Test for LibMPUIO) +dnl ============================================================ +dnl Check for LibMPU +dnl ============================================================ +AC_CHECK_LIBMPU( + [1.0.25], + [yes], + [ + AC_DEFINE( + [HAVE_LIBMPU], + [1], + [Define to 1 if LibMPU is available.] + ) + LIBMPU_VERSION=`$LIBMPU_CONFIG --version 2>/dev/null` + AC_SUBST([LIBMPU_VERSION]) + ], + [ + AC_MSG_ERROR([LibMPU version 1.0.25 or later is required.]) + ] +) + +dnl ============================================================ +dnl Check for LibMPUIO +dnl ============================================================ +AC_CHECK_LIBMPUIO( + [1.0.4], + [yes], + [ + AC_DEFINE( + [HAVE_LIBMPUIO], + [1], + [Define to 1 if LibMPUIO is available.] + ) + LIBMPUIO_VERSION=`$LIBMPUIO_CONFIG --version 2>/dev/null` + AC_SUBST([LIBMPUIO_VERSION]) + ], + [ + AC_MSG_ERROR([LibMPUIO version 1.0.4 or later is required.]) + ] +) + +AC_MSG_CFG_PART(Select HOST sysdeps) +MCPU_SYSDEP_DIR_NAMES + +AC_MSG_CFG_PART(Check required HOST errno translator) +mcpu_errno_sysdep_found=no +mcpu_errno_sysdep_path= +for mcpu_sysdir in $sysnames; do + if test -f "$srcdir/$mcpu_sysdir/sys-errno.c"; then + mcpu_errno_sysdep_found=yes + mcpu_errno_sysdep_path="$mcpu_sysdir" + break + fi +done +AC_MSG_CHECKING([LibMCPU sysdeps for HOST errno translation]) +if test "x$mcpu_errno_sysdep_found" = xyes; then + AC_MSG_RESULT([$mcpu_errno_sysdep_path]) +else + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([LibMCPU does not support host $host: required sysdep implementation for __cpu_errno_from_host() was not found.]) +fi + +AC_MSG_CFG_PART(Check required syscall sysdeps) +for mcpu_syscall in open creat close dup read write lseek link unlink chdir mkdir chmod chown access rename rmdir symlink readlink stat fstat lstat fcntl pipe2 getdents select fsync truncate ftruncate fchmod fchown fchdir clock_gettime clock_getres clock_nanosleep; do + mcpu_sysdep_found=no + mcpu_sysdep_path= + for mcpu_sysdir in $sysnames; do + if test -f "$srcdir/$mcpu_sysdir/sys-$mcpu_syscall.c"; then + mcpu_sysdep_found=yes + mcpu_sysdep_path="$mcpu_sysdir" + break + fi + done + AC_MSG_CHECKING([LibMCPU sysdeps for $mcpu_syscall]) + if test "x$mcpu_sysdep_found" = xyes; then + AC_MSG_RESULT([$mcpu_sysdep_path]) + else + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([LibMCPU does not support host $host: required sysdep implementation for __cpu_sys_$mcpu_syscall() was not found.]) + fi +done + +AC_MSG_CFG_PART(Define MCPU installation layout) + +mcpu_prefix="$prefix" +if test "x$mcpu_prefix" = xNONE; then + mcpu_prefix="$ac_default_prefix" +fi + +mcpu_exec_prefix="$exec_prefix" +if test "x$mcpu_exec_prefix" = xNONE; then + mcpu_exec_prefix="$mcpu_prefix" +fi + +mcpu_save_prefix="$prefix" +mcpu_save_exec_prefix="$exec_prefix" +prefix="$mcpu_prefix" +exec_prefix="$mcpu_exec_prefix" +eval mcpu_bindir="\"$bindir\"" +eval mcpu_libdir="\"$libdir\"" +prefix="$mcpu_save_prefix" +exec_prefix="$mcpu_save_exec_prefix" + +mcpu_root="$mcpu_libdir/mcpu" +mcpu_private_bindir="$mcpu_root/bin" +mcpu_etcdir="$mcpu_root/etc" + +MCPU_ROOT="$mcpu_root" +MCPU_BINDIR="$mcpu_private_bindir" +MCPU_ETCDIR="$mcpu_etcdir" +AC_SUBST([MCPU_ROOT]) +AC_SUBST([MCPU_BINDIR]) +AC_SUBST([MCPU_ETCDIR]) + +mcpu_public_link_root="$mcpu_private_bindir" +case "$mcpu_bindir:$mcpu_private_bindir" in + "$mcpu_exec_prefix"/*:"$mcpu_exec_prefix"/*) + mcpu_bindir_rel=${mcpu_bindir#"$mcpu_exec_prefix/"} + mcpu_target_rel=${mcpu_private_bindir#"$mcpu_exec_prefix/"} + mcpu_up= + mcpu_rest=$mcpu_bindir_rel + while :; do + mcpu_up="../$mcpu_up" + case "$mcpu_rest" in + */*) mcpu_rest=${mcpu_rest#*/} ;; + *) break ;; + esac + done + mcpu_public_link_root="$mcpu_up$mcpu_target_rel" + ;; +esac + +MCPU_AS_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-as" +MCPU_LD_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-ld" +MCPU_RUN_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-run" +MCPU_READELF_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-readelf" +MCPU_AR_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-ar" +MCPU_NM_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-nm" +MCPU_OBJDUMP_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-objdump" +MCPU_STRIP_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-strip" +MCPU_OBJCOPY_PUBLIC_LINK_TARGET="$mcpu_public_link_root/mcpu-objcopy" + +AC_SUBST([MCPU_AS_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_LD_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_RUN_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_READELF_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_AR_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_NM_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_OBJDUMP_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_STRIP_PUBLIC_LINK_TARGET]) +AC_SUBST([MCPU_OBJCOPY_PUBLIC_LINK_TARGET]) + +AC_MSG_CFG_PART(OUTPUT Substitutions) +AC_CONFIG_FILES([ Makefile + cpu/Makefile + utils/Makefile + sysroot/Makefile + tests/Makefile + man/Makefile + man/ru/Makefile + mcpu-config + mcpu.pc ]) +AC_OUTPUT + +AC_MSG_CFG_PART(Configuration summary) +AC_MSG_NOTICE([LibMCPU configuration:]) +AC_MSG_NOTICE([ version : $PACKAGE_VERSION]) +AC_MSG_NOTICE([ host : $host]) +AC_MSG_NOTICE([ host base machine : $base_machine]) +AC_MSG_NOTICE([ host machine : $MCPU_HOST_MACHINE]) +AC_MSG_NOTICE([ host data model : $MCPU_HOST_DATA_MODEL]) +AC_MSG_NOTICE([ sysdeps : $sysnames]) +AC_MSG_NOTICE([ prefix : $prefix]) +AC_MSG_NOTICE([ library directory : $mcpu_libdir]) +AC_MSG_NOTICE([ install MCPU root : $mcpu_root]) +AC_MSG_NOTICE([ private commands : $mcpu_private_bindir]) +AC_MSG_NOTICE([ MCPU configuration : $mcpu_etcdir/mcpu-ld.conf]) +AC_MSG_NOTICE([ public mcpu-as : $mcpu_bindir/mcpu-as]) +AC_MSG_NOTICE([ public symlink : $MCPU_AS_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-ld : $mcpu_bindir/mcpu-ld]) +AC_MSG_NOTICE([ public symlink : $MCPU_LD_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-run : $mcpu_bindir/mcpu-run]) +AC_MSG_NOTICE([ public symlink : $MCPU_RUN_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-readelf : $mcpu_bindir/mcpu-readelf]) +AC_MSG_NOTICE([ public symlink : $MCPU_READELF_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-ar : $mcpu_bindir/mcpu-ar]) +AC_MSG_NOTICE([ public symlink : $MCPU_AR_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-nm : $mcpu_bindir/mcpu-nm]) +AC_MSG_NOTICE([ public symlink : $MCPU_NM_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-objdump : $mcpu_bindir/mcpu-objdump]) +AC_MSG_NOTICE([ public symlink : $MCPU_OBJDUMP_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-strip : $mcpu_bindir/mcpu-strip]) +AC_MSG_NOTICE([ public symlink : $MCPU_STRIP_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ public mcpu-objcopy : $mcpu_bindir/mcpu-objcopy]) +AC_MSG_NOTICE([ public symlink : $MCPU_OBJCOPY_PUBLIC_LINK_TARGET]) +AC_MSG_NOTICE([ mpu-config : $LIBMPU_CONFIG]) +AC_MSG_NOTICE([ mpuio-config : $LIBMPUIO_CONFIG]) +AC_MSG_NOTICE([ LibMPU version : $LIBMPU_VERSION]) +AC_MSG_NOTICE([ LibMPUIO version : $LIBMPUIO_VERSION]) +AC_MSG_NOTICE([ LibMPU CPPFLAGS : $LIBMPU_CPPFLAGS]) +AC_MSG_NOTICE([ LibMPU CFLAGS : $LIBMPU_CFLAGS]) +AC_MSG_NOTICE([ LibMPU LDFLAGS : $LIBMPU_LDFLAGS]) +AC_MSG_NOTICE([ LibMPU LIBS : $LIBMPU_LIBS]) +AC_MSG_NOTICE([ LibMPUIO CPPFLAGS : $LIBMPUIO_CPPFLAGS]) +AC_MSG_NOTICE([ LibMPUIO CFLAGS : $LIBMPUIO_CFLAGS]) +AC_MSG_NOTICE([ LibMPUIO LDFLAGS : $LIBMPUIO_LDFLAGS]) +AC_MSG_NOTICE([ LibMPUIO LIBS : $LIBMPUIO_LIBS]) + +if test -f "config.h"; then + echo "" + echo "Now you can run:" + echo " \`${TB}make${TN}' to compile," + echo " \`${TB}make tests${TN}' to run the test suite," + echo " \`${TB}make install${TN}' to install ${TB}LibMCPU${TN}," + echo " \`${TB}make dist${TN}' to create distributable tarballs, or" + echo " \`${TB}make distclean${TN}' to clean before configure for another target." + echo "Enjoy." + echo "" +fi diff --git a/cpu/Makefile.am b/cpu/Makefile.am new file mode 100644 index 0000000..c3a3eab --- /dev/null +++ b/cpu/Makefile.am @@ -0,0 +1,162 @@ + +sysnames = @sysnames@ + +vpath %.c $(addprefix $(top_srcdir)/, $(sysnames)) + +lib_LTLIBRARIES = libmcpu.la + +libmcpu_la_SOURCES = cpu-context.c cpu-context.h cpu-registers.c cpu-registers.h cpu-byteorder.c cpu-instruction.c cpu-instruction.h cpu-syscalls.c cpu-syscalls.h cpu-sysdeps.h cpu-errno.h + +nodist_libmcpu_la_SOURCES = sys-errno.c sys-open.c sys-creat.c sys-close.c sys-dup.c sys-read.c sys-write.c sys-lseek.c \ + sys-link.c sys-unlink.c sys-chdir.c sys-mkdir.c sys-chmod.c sys-chown.c sys-access.c sys-rename.c sys-rmdir.c \ + sys-symlink.c sys-readlink.c sys-stat.c sys-fstat.c sys-lstat.c sys-fcntl.c sys-pipe2.c sys-getdents.c sys-select.c sys-fsync.c sys-truncate.c sys-ftruncate.c sys-fchmod.c sys-fchown.c sys-fchdir.c sys-clock_gettime.c sys-clock_getres.c sys-clock_nanosleep.c + +libmcpu_la_CPPFLAGS = -I$(top_srcdir)/include $(LIBMPU_CPPFLAGS) +libmcpu_la_CFLAGS = $(LIBMPU_CFLAGS) +libmcpu_la_LIBADD = $(LIBMPU_LIBS) +libmcpu_la_LDFLAGS = -version-info @LIBMCPU_LT_VERSION_INFO@ $(LIBMPU_LDFLAGS) + +# Automake's generated libtool compile rules rebuild a source pathname from +# $(srcdir) and therefore lose the pathname selected by GNU make vpath. +# These explicit rules compile the prerequisite path resolved by vpath ($<). + +libmcpu_la-sys-errno.lo: sys-errno.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-errno.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-errno.Tpo $(DEPDIR)/libmcpu_la-sys-errno.Plo + +libmcpu_la-sys-open.lo: sys-open.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-open.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-open.Tpo $(DEPDIR)/libmcpu_la-sys-open.Plo + +libmcpu_la-sys-creat.lo: sys-creat.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-creat.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-creat.Tpo $(DEPDIR)/libmcpu_la-sys-creat.Plo + +libmcpu_la-sys-close.lo: sys-close.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-close.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-close.Tpo $(DEPDIR)/libmcpu_la-sys-close.Plo + +libmcpu_la-sys-dup.lo: sys-dup.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-dup.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-dup.Tpo $(DEPDIR)/libmcpu_la-sys-dup.Plo + +libmcpu_la-sys-read.lo: sys-read.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-read.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-read.Tpo $(DEPDIR)/libmcpu_la-sys-read.Plo + +libmcpu_la-sys-write.lo: sys-write.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-write.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-write.Tpo $(DEPDIR)/libmcpu_la-sys-write.Plo + +libmcpu_la-sys-lseek.lo: sys-lseek.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-lseek.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-lseek.Tpo $(DEPDIR)/libmcpu_la-sys-lseek.Plo + +libmcpu_la-sys-link.lo: sys-link.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-link.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-link.Tpo $(DEPDIR)/libmcpu_la-sys-link.Plo + +libmcpu_la-sys-unlink.lo: sys-unlink.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-unlink.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-unlink.Tpo $(DEPDIR)/libmcpu_la-sys-unlink.Plo + +libmcpu_la-sys-chdir.lo: sys-chdir.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-chdir.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-chdir.Tpo $(DEPDIR)/libmcpu_la-sys-chdir.Plo + +libmcpu_la-sys-mkdir.lo: sys-mkdir.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-mkdir.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-mkdir.Tpo $(DEPDIR)/libmcpu_la-sys-mkdir.Plo + +libmcpu_la-sys-access.lo: sys-access.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-access.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-access.Tpo $(DEPDIR)/libmcpu_la-sys-access.Plo + +libmcpu_la-sys-rename.lo: sys-rename.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-rename.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-rename.Tpo $(DEPDIR)/libmcpu_la-sys-rename.Plo + +libmcpu_la-sys-rmdir.lo: sys-rmdir.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-rmdir.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-rmdir.Tpo $(DEPDIR)/libmcpu_la-sys-rmdir.Plo + +libmcpu_la-sys-symlink.lo: sys-symlink.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-symlink.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-symlink.Tpo $(DEPDIR)/libmcpu_la-sys-symlink.Plo + +libmcpu_la-sys-readlink.lo: sys-readlink.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-readlink.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-readlink.Tpo $(DEPDIR)/libmcpu_la-sys-readlink.Plo + +libmcpu_la-sys-stat.lo: sys-stat.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-stat.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-stat.Tpo $(DEPDIR)/libmcpu_la-sys-stat.Plo + +libmcpu_la-sys-fstat.lo: sys-fstat.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fstat.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fstat.Tpo $(DEPDIR)/libmcpu_la-sys-fstat.Plo + +libmcpu_la-sys-lstat.lo: sys-lstat.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-lstat.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-lstat.Tpo $(DEPDIR)/libmcpu_la-sys-lstat.Plo + +libmcpu_la-sys-chmod.lo: sys-chmod.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-chmod.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-chmod.Tpo $(DEPDIR)/libmcpu_la-sys-chmod.Plo + +libmcpu_la-sys-chown.lo: sys-chown.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-chown.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-chown.Tpo $(DEPDIR)/libmcpu_la-sys-chown.Plo + +libmcpu_la-sys-fcntl.lo: sys-fcntl.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fcntl.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fcntl.Tpo $(DEPDIR)/libmcpu_la-sys-fcntl.Plo + +libmcpu_la-sys-pipe2.lo: sys-pipe2.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-pipe2.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-pipe2.Tpo $(DEPDIR)/libmcpu_la-sys-pipe2.Plo + +libmcpu_la-sys-getdents.lo: sys-getdents.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-getdents.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-getdents.Tpo $(DEPDIR)/libmcpu_la-sys-getdents.Plo + + +libmcpu_la-sys-select.lo: sys-select.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-select.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-select.Tpo $(DEPDIR)/libmcpu_la-sys-select.Plo + +libmcpu_la-sys-fsync.lo: sys-fsync.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fsync.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fsync.Tpo $(DEPDIR)/libmcpu_la-sys-fsync.Plo + +libmcpu_la-sys-truncate.lo: sys-truncate.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-truncate.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-truncate.Tpo $(DEPDIR)/libmcpu_la-sys-truncate.Plo + +libmcpu_la-sys-ftruncate.lo: sys-ftruncate.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-ftruncate.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-ftruncate.Tpo $(DEPDIR)/libmcpu_la-sys-ftruncate.Plo + +libmcpu_la-sys-fchmod.lo: sys-fchmod.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fchmod.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fchmod.Tpo $(DEPDIR)/libmcpu_la-sys-fchmod.Plo + +libmcpu_la-sys-fchown.lo: sys-fchown.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fchown.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fchown.Tpo $(DEPDIR)/libmcpu_la-sys-fchown.Plo + +libmcpu_la-sys-fchdir.lo: sys-fchdir.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-fchdir.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-fchdir.Tpo $(DEPDIR)/libmcpu_la-sys-fchdir.Plo + +libmcpu_la-sys-clock_gettime.lo: sys-clock_gettime.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-clock_gettime.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-clock_gettime.Tpo $(DEPDIR)/libmcpu_la-sys-clock_gettime.Plo + +libmcpu_la-sys-clock_getres.lo: sys-clock_getres.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-clock_getres.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-clock_getres.Tpo $(DEPDIR)/libmcpu_la-sys-clock_getres.Plo + +libmcpu_la-sys-clock_nanosleep.lo: sys-clock_nanosleep.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmcpu_la_CPPFLAGS) $(CPPFLAGS) $(libmcpu_la_CFLAGS) $(CFLAGS) -MT $@ -MD -MP -MF $(DEPDIR)/libmcpu_la-sys-clock_nanosleep.Tpo -c -o $@ $< + $(AM_V_at)$(am__mv) $(DEPDIR)/libmcpu_la-sys-clock_nanosleep.Tpo $(DEPDIR)/libmcpu_la-sys-clock_nanosleep.Plo diff --git a/cpu/cpu-byteorder.c b/cpu/cpu-byteorder.c new file mode 100644 index 0000000..ebc612a --- /dev/null +++ b/cpu/cpu-byteorder.c @@ -0,0 +1,116 @@ +/*************************************************************** + __CPU_BYTEORDER.C + + Byte-order accessors for canonical PROGRAM fields and + architectural data-object representation. + + PART OF : CPU - library . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#include <string.h> +#include <libmcpu.h> + +__mpu_uint16_t +__cpu_data_get_u16( const __mpu_UNIT_t *p ) +{ + __mpu_uint16_t v = 0; + if( p ) memcpy( &v, p, sizeof(v) ); + return( v ); +} + +__mpu_uint32_t +__cpu_data_get_u32( const __mpu_UNIT_t *p ) +{ + __mpu_uint32_t v = 0; + if( p ) memcpy( &v, p, sizeof(v) ); + return( v ); +} + +__mpu_uint64_t +__cpu_data_get_u64( const __mpu_UNIT_t *p ) +{ + __mpu_uint64_t v = 0; + if( p ) memcpy( &v, p, sizeof(v) ); + return( v ); +} + +void +__cpu_data_put_u16( __mpu_UNIT_t *p, __mpu_uint16_t v ) +{ + if( p ) memcpy( p, &v, sizeof(v) ); +} + +void +__cpu_data_put_u32( __mpu_UNIT_t *p, __mpu_uint32_t v ) +{ + if( p ) memcpy( p, &v, sizeof(v) ); +} + +void +__cpu_data_put_u64( __mpu_UNIT_t *p, __mpu_uint64_t v ) +{ + if( p ) memcpy( p, &v, sizeof(v) ); +} + +__mpu_uint16_t +__cpu_code_get_u16( const __mpu_UNIT_t *p ) +{ + if( !p ) return( 0 ); + return (__mpu_uint16_t)(((__mpu_uint16_t)p[0] << 8) | + (__mpu_uint16_t)p[1]); +} + +__mpu_uint32_t +__cpu_code_get_u32( const __mpu_UNIT_t *p ) +{ + __mpu_uint32_t v; + if( !p ) return( 0 ); + v = ((__mpu_uint32_t)p[0] << 24); + v |= ((__mpu_uint32_t)p[1] << 16); + v |= ((__mpu_uint32_t)p[2] << 8); + v |= (__mpu_uint32_t)p[3]; + return( v ); +} + +__mpu_uint64_t +__cpu_code_get_u64( const __mpu_UNIT_t *p ) +{ + __mpu_uint64_t v = 0; + int k; + if( !p ) return( 0 ); + for( k = 0; k < 8; ++k ) v = ( v << 8 ) | p[k]; + return( v ); +} + +void +__cpu_code_put_u16( __mpu_UNIT_t *p, __mpu_uint16_t v ) +{ + if( !p ) return; + p[0] = (__mpu_UNIT_t)(v >> 8); + p[1] = (__mpu_UNIT_t)v; +} + +void +__cpu_code_put_u32( __mpu_UNIT_t *p, __mpu_uint32_t v ) +{ + if( !p ) return; + p[0] = (__mpu_UNIT_t)(v >> 24); + p[1] = (__mpu_UNIT_t)(v >> 16); + p[2] = (__mpu_UNIT_t)(v >> 8); + p[3] = (__mpu_UNIT_t)v; +} + +void +__cpu_code_put_u64( __mpu_UNIT_t *p, __mpu_uint64_t v ) +{ + int k; + if( !p ) return; + for( k = 7; k >= 0; --k ) + { + p[k] = (__mpu_UNIT_t)( v & __mpu_UINT64_C( 0xff ) ); + v >>= 8; + } +} diff --git a/cpu/cpu-context.c b/cpu/cpu-context.c new file mode 100644 index 0000000..e77d93a --- /dev/null +++ b/cpu/cpu-context.c @@ -0,0 +1,589 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#include <libmcpu.h> +#include <cpu-context.h> +#include <cpu-syscalls.h> + +__thread struct __cpu_context cctx; +__thread struct __cpu_context *pcctx; + +static __mpu_uint32_t +__cpu_get_mpu_psw( void ) +{ + __mpu_uint32_t psw = 0; + if( __mpu_gta() ) psw |= PSW_AF_MASK; + if( __mpu_gtc() ) psw |= PSW_CF_MASK; + if( __mpu_gto() ) psw |= PSW_OF_MASK; + if( __mpu_gts() ) psw |= PSW_SF_MASK; + if( __mpu_gtp() ) psw |= PSW_PF_MASK; + if( __mpu_gtz() ) psw |= PSW_ZF_MASK; + if( __mpu_gtr() ) psw |= PSW_RF_MASK; + if( __mpu_gtv() ) psw |= PSW_VF_MASK; + if( __mpu_gtdom() ) psw |= PSW_DOMF_MASK; + if( __mpu_gtsng() ) psw |= PSW_SNGF_MASK; + if( __mpu_gtovf() ) psw |= PSW_OVFF_MASK; + if( __mpu_gtudf() ) psw |= PSW_UDFF_MASK; + if( __mpu_gttls() ) psw |= PSW_TLSF_MASK; + if( __mpu_gtpls() ) psw |= PSW_PLSF_MASK; + if( __mpu_gtind() ) psw |= PSW_INDF_MASK; + if( __mpu_gtinx() ) psw |= PSW_INXF_MASK; + return( psw ); +} + +static void +__cpu_set_mpu_psw( __mpu_uint32_t p ) +{ +#define SF(mask,st,cl) do{if(p&(mask)) st(); else cl();}while(0) + SF( PSW_AF_MASK, __mpu_sta, __mpu_cla ); + SF( PSW_CF_MASK, __mpu_stc, __mpu_clc ); + SF( PSW_OF_MASK, __mpu_sto, __mpu_clo ); + SF( PSW_SF_MASK, __mpu_sts, __mpu_cls ); + SF( PSW_PF_MASK, __mpu_stp, __mpu_clp ); + SF( PSW_ZF_MASK, __mpu_stz, __mpu_clz ); + SF( PSW_RF_MASK, __mpu_str, __mpu_clr ); + SF( PSW_VF_MASK, __mpu_stv, __mpu_clv ); + SF( PSW_DOMF_MASK, __mpu_stdom, __mpu_cldom ); + SF( PSW_SNGF_MASK, __mpu_stsng, __mpu_clsng ); + SF( PSW_OVFF_MASK, __mpu_stovf, __mpu_clovf ); + SF( PSW_UDFF_MASK, __mpu_studf, __mpu_cludf ); + SF( PSW_TLSF_MASK, __mpu_sttls, __mpu_cltls ); + SF( PSW_PLSF_MASK, __mpu_stpls, __mpu_clpls ); + SF( PSW_INDF_MASK, __mpu_stind, __mpu_clind ); + SF( PSW_INXF_MASK, __mpu_stinx, __mpu_clinx ); +#undef SF +} + +/* + * Host backing store. Architectural size and host mapping capacity are kept + * separate deliberately: the latter is rounded to the host page size only so + * mmap()/mprotect()/munmap() can operate on complete pages. CPU bounds checks + * must always use the architectural size fields. + */ +static size_t +host_page_size( void ) +{ + static size_t page; + long n; + if( page ) return( page ); + n = sysconf( _SC_PAGESIZE ); + if( n <= 0 ) return( 0 ); + page = (size_t)n; + return( page ); +} + +static void +mem_unmap( __mpu_UNIT_t **p, __mpu_uint64_t *sz, __mpu_uint64_t *cap ) +{ + if( *p && *cap && (__mpu_uint64_t)(size_t)*cap == *cap ) + (void)munmap( (void *)*p, (size_t)*cap ); + *p = 0; + *sz = 0; + *cap = 0; +} + +static int mem_alloc( __mpu_UNIT_t **p, __mpu_uint64_t *sz, __mpu_uint64_t *cap, + __mpu_uint64_t n) +{ + size_t page, need, mapped, max = (size_t)-1; + void *q; + + mem_unmap( p, sz, cap ); + if( !n ) return( 0 ); + if( (__mpu_uint64_t)(size_t)n != n ) return( -1 ); + + page = host_page_size(); + if( !page ) return( -1 ); + need = (size_t)n; + if( need > max - ( page - 1U ) ) return( -1 ); + mapped = ( ( need + page - 1U ) / page ) * page; + if( !mapped ) return( -1 ); + + q = mmap( (void *)0, mapped, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if( q == MAP_FAILED ) return( -1 ); + + /* Anonymous mappings are zero-filled by the host kernel. */ + *p = (__mpu_UNIT_t *)q; + *sz = n; + *cap = (__mpu_uint64_t)mapped; + return( 0 ); +} + +void +__cpu_init( void ) +{ + pcctx = &cctx; + __cpu_cwd_context_free(); + __cpu_fd_context_free(); + mem_unmap( &cctx._program, &cctx._program_size, &cctx._program_capacity ); + mem_unmap( &cctx._data, &cctx._data_size, &cctx._data_capacity ); + mem_unmap( &cctx._stack, &cctx._stack_size, &cctx._stack_capacity ); + mem_unmap( &cctx._control, &cctx._control_size, &cctx._control_capacity ); + mem_unmap( &cctx._input, &cctx._input_size, &cctx._input_capacity ); + mem_unmap( &cctx._output, &cctx._output_size, &cctx._output_capacity ); + mem_unmap( &cctx._trace, &cctx._trace_size, &cctx._trace_capacity ); + mem_unmap( &cctx._heap, &cctx._heap_size, &cctx._heap_capacity ); + memset( &cctx, 0, sizeof( cctx ) ); + pcctx = &cctx; + __cpu_fd_context_init(); + __cpu_cwd_context_init(); + __mpu_init(); +} + +void +__cpu_free_context( void ) +{ + if( !pcctx ) pcctx = &cctx; + __cpu_cwd_context_free(); + __cpu_fd_context_free(); + mem_unmap( &pcctx->_program, &pcctx->_program_size, &pcctx->_program_capacity ); + mem_unmap( &pcctx->_data, &pcctx->_data_size, &pcctx->_data_capacity ); + mem_unmap( &pcctx->_stack, &pcctx->_stack_size, &pcctx->_stack_capacity ); + mem_unmap( &pcctx->_control, &pcctx->_control_size, &pcctx->_control_capacity ); + mem_unmap( &pcctx->_input, &pcctx->_input_size, &pcctx->_input_capacity ); + mem_unmap( &pcctx->_output, &pcctx->_output_size, &pcctx->_output_capacity ); + mem_unmap( &pcctx->_trace, &pcctx->_trace_size, &pcctx->_trace_capacity ); + mem_unmap( &pcctx->_heap, &pcctx->_heap_size, &pcctx->_heap_capacity ); + pcctx->_heap_brk = 0; + pcctx->_heap_limit = 0; + pcctx->_pc = 0; + pcctx->_sp = 0; + pcctx->_fp = 0; + pcctx->_psw = 0; + pcctx->_stop_reason = CPU_STOP_NONE; + pcctx->_exit_status = 0; + __mpu_free_context(); +} + +__mpu_uint64_t * +__ptr_cpu_pc( void ) +{ + return( &cctx._pc ); +} + +__mpu_uint64_t * +__ptr_cpu_sp( void ) +{ + return( &cctx._sp ); +} + +__mpu_uint64_t * +__ptr_cpu_fp( void ) +{ + return( &cctx._fp ); +} + +__mpu_UNIT_t * +__ptr_cpu_registers( void ) +{ + return( &cctx._regs[0] ); +} + +int +__cpu_program_alloc( __mpu_uint64_t n ) +{ + return( mem_alloc( &cctx._program, &cctx._program_size, &cctx._program_capacity, n ) ); +} + +void +__cpu_program_free( void ) +{ + mem_unmap( &cctx._program, &cctx._program_size, &cctx._program_capacity ); + cctx._pc = 0; +} + +int +__cpu_program_protect( void ) +{ + if( !cctx._program ) return( cctx._program_size == 0 ? 0 : -1 ); + if( !cctx._program_capacity || + (__mpu_uint64_t)(size_t)cctx._program_capacity != cctx._program_capacity) + return( -1 ); + return( mprotect( (void *)cctx._program, (size_t)cctx._program_capacity, PROT_READ ) == 0 ? 0 : -1 ); +} + +__mpu_UNIT_t * +__cpu_program_ptr( void ) +{ + return( cctx._program ); +} + +__mpu_uint64_t +__cpu_program_size( void ) +{ + return( cctx._program_size ); +} + +int +__cpu_program_set( const __mpu_UNIT_t *d, __mpu_uint64_t n ) +{ + if( __cpu_program_alloc( n ) ) return( -1 ); + if( n && d ) memcpy( cctx._program, d, (size_t)n ); + if( __cpu_program_protect() ) + { + __cpu_program_free(); + return( -1 ); + } + return( 0 ); +} + +int +__cpu_data_alloc( __mpu_uint64_t n ) +{ + return( mem_alloc( &cctx._data, &cctx._data_size, &cctx._data_capacity, n ) ); +} + +void +__cpu_data_free( void ) +{ + mem_unmap( &cctx._data, &cctx._data_size, &cctx._data_capacity ); +} + +__mpu_UNIT_t * +__cpu_data_ptr( void ) +{ + return( cctx._data ); +} + +__mpu_uint64_t +__cpu_data_size( void ) +{ + return( cctx._data_size ); +} + +int +__cpu_data_set( const __mpu_UNIT_t *d, __mpu_uint64_t n ) +{ + if( __cpu_data_alloc( n ) ) return( -1 ); + if( n && d ) memcpy( cctx._data, d, (size_t)n ); + return( 0 ); +} + +int +__cpu_stack_alloc( __mpu_uint64_t n ) +{ + int rc = mem_alloc( &cctx._stack, &cctx._stack_size, &cctx._stack_capacity, n ); + cctx._sp = ( rc == 0 ) ? n : 0; + cctx._fp = ( rc == 0 ) ? n : 0; + return( rc ); +} + +void +__cpu_stack_free( void ) +{ + mem_unmap( &cctx._stack, &cctx._stack_size, &cctx._stack_capacity ); + cctx._sp = 0; + cctx._fp = 0; +} + +__mpu_UNIT_t * +__cpu_stack_ptr( void ) +{ + return( cctx._stack ); +} + +__mpu_uint64_t +__cpu_stack_size( void ) +{ + return( cctx._stack_size ); +} + +#define DEFINE_SEGMENT_API(name,field) \ +int __cpu_##name##_alloc(__mpu_uint64_t n){return mem_alloc(&cctx._##field,&cctx._##field##_size,&cctx._##field##_capacity,n);} \ +void __cpu_##name##_free(void){mem_unmap(&cctx._##field,&cctx._##field##_size,&cctx._##field##_capacity);} \ +__mpu_UNIT_t *__cpu_##name##_ptr(void){return cctx._##field;} \ +__mpu_uint64_t __cpu_##name##_size(void){return cctx._##field##_size;} \ +int __cpu_##name##_set(const __mpu_UNIT_t *d,__mpu_uint64_t n){if(__cpu_##name##_alloc(n))return -1;if(n&&d)memcpy(cctx._##field,d,(size_t)n);return 0;} + +DEFINE_SEGMENT_API( control, control ) +DEFINE_SEGMENT_API( input, input ) +DEFINE_SEGMENT_API( output, output ) +DEFINE_SEGMENT_API( trace, trace ) +#undef DEFINE_SEGMENT_API + +static int +__cpu_heap_grow( __mpu_uint64_t new_size ) +{ + size_t page, need, mapped, max = (size_t)-1; + __mpu_UNIT_t *p; + + if( new_size <= cctx._heap_capacity ) return( 0 ); + if( (__mpu_uint64_t)(size_t)new_size != new_size ) return( -1 ); + + page = host_page_size(); + if( !page ) return( -1 ); + + need = (size_t)new_size; + if( need > max - (page - 1U) ) return( -1 ); + + mapped = ((need + page - 1U) / page) * page; + if( !mapped ) return( -1 ); + + p = (__mpu_UNIT_t *)mmap( (void *)0, mapped, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 ); + if( p == (__mpu_UNIT_t *)MAP_FAILED ) return( -1 ); + + if( cctx._heap && cctx._heap_size ) + memcpy( p, cctx._heap, (size_t)cctx._heap_size ); + + if( cctx._heap && cctx._heap_capacity ) + (void)munmap( (void *)cctx._heap, (size_t)cctx._heap_capacity ); + + cctx._heap = p; + cctx._heap_capacity = (__mpu_uint64_t)mapped; + + return( 0 ); + +} /* End of __cpu_heap_grow() */ + + +int +__cpu_heap_alloc( __mpu_uint64_t initial_size, __mpu_uint64_t maximum_size ) +{ + int rc; + + if( maximum_size < initial_size || + maximum_size < CPU_HEAP_BEGIN || initial_size < CPU_HEAP_BEGIN ) + return( -1 ); + + rc = mem_alloc( &cctx._heap, &cctx._heap_size, + &cctx._heap_capacity, initial_size ); + if( rc ) + { + cctx._heap_brk = 0; + cctx._heap_limit = 0; + return( rc ); + } + + cctx._heap_brk = CPU_HEAP_BEGIN; + cctx._heap_limit = maximum_size; + + return( 0 ); + +} /* End of __cpu_heap_alloc() */ + + +void +__cpu_heap_free( void ) +{ + mem_unmap( &cctx._heap, &cctx._heap_size, &cctx._heap_capacity ); + cctx._heap_brk = 0; + cctx._heap_limit = 0; + +} /* End of __cpu_heap_free() */ + + +__mpu_uint64_t +__cpu_heap_brk( __mpu_uint64_t new_brk ) +{ + __mpu_uint64_t old_brk = cctx._heap_brk; + + if( !cctx._heap || cctx._heap_limit < CPU_HEAP_BEGIN ) + return( old_brk ); + + if( new_brk < CPU_HEAP_BEGIN || new_brk > cctx._heap_limit ) + return( old_brk ); + + if( new_brk == old_brk ) return( old_brk ); + + if( new_brk < old_brk ) + { + memset( cctx._heap + (size_t)new_brk, 0, + (size_t)(old_brk - new_brk) ); + cctx._heap_brk = new_brk; + return( new_brk ); + } + + if( new_brk <= cctx._heap_size ) + { + cctx._heap_brk = new_brk; + return( new_brk ); + } + + if( new_brk <= cctx._heap_capacity ) + { + memset( cctx._heap + (size_t)cctx._heap_size, 0, + (size_t)(new_brk - cctx._heap_size) ); + cctx._heap_size = new_brk; + cctx._heap_brk = new_brk; + return( new_brk ); + } + + if( __cpu_heap_grow( new_brk ) != 0 ) return( old_brk ); + + cctx._heap_size = new_brk; + cctx._heap_brk = new_brk; + + return( new_brk ); + +} /* End of __cpu_heap_brk() */ + + +__mpu_UNIT_t * +__cpu_heap_ptr( void ) +{ + return( cctx._heap ); +} + +__mpu_uint64_t +__cpu_heap_size( void ) +{ + return( cctx._heap_size ); +} + +__mpu_uint64_t +__cpu_heap_limit( void ) +{ + return( cctx._heap_limit ); +} + +cpu_stop_reason +__cpu_get_stop_reason( void ) +{ + return( cctx._stop_reason ); +} + +__mpu_int32_t +__cpu_get_exit_status( void ) +{ + return( cctx._exit_status ); +} + +void +__cpu_clear_stop_reason( void ) +{ + cctx._stop_reason = CPU_STOP_NONE; + cctx._exit_status = 0; +} + +__mpu_uint32_t +__cpu_get_psw( void ) +{ + return( ( __cpu_get_mpu_psw() & PSW_MPU_MASK ) | ( cctx._psw & PSW_CPU_MASK ) ); +} + +void +__cpu_set_psw( __mpu_uint32_t p ) +{ + __cpu_set_mpu_psw( p & PSW_MPU_MASK ); + cctx._psw = p & PSW_CPU_MASK; +} + +void +__cpu_clear_psw( void ) +{ + __mpu_clear_mflags(); + cctx._psw = 0; +} + +int +__cpu_gth( void ) +{ + return( (int)__CFLAG( PSW_HF_MASK, PSW_HF_SHIFT ) ); +} + +int +__cpu_gtt( void ) +{ + return( (int)__CFLAG( PSW_TF_MASK, PSW_TF_SHIFT ) ); +} + +int +__cpu_gti( void ) +{ + return( (int)__CFLAG( PSW_IF_MASK, PSW_IF_SHIFT ) ); +} + +void +__cpu_sth( void ) +{ + __SET_CFLAG( PSW_HF_MASK ); +} + +void +__cpu_stt( void ) +{ + __SET_CFLAG( PSW_TF_MASK ); +} + +void +__cpu_sti( void ) +{ + __SET_CFLAG( PSW_IF_MASK ); +} + +void +__cpu_clh( void ) +{ + __CLEAR_CFLAG( PSW_HF_MASK ); +} + +void +__cpu_clt( void ) +{ + __CLEAR_CFLAG( PSW_TF_MASK ); +} + +void +__cpu_cli( void ) +{ + __CLEAR_CFLAG( PSW_IF_MASK ); +} + +void +__cpu_cmh( void ) +{ + __INVERT_CFLAG( PSW_HF_MASK ); +} + +void +__cpu_cmt( void ) +{ + __INVERT_CFLAG( PSW_TF_MASK ); +} + +void +__cpu_cmi( void ) +{ + __INVERT_CFLAG( PSW_IF_MASK ); +} + +int +__cpu_gtmode( void ) +{ + return( (int)( ( cctx._psw & PSW_MODE_MASK ) >> PSW_MODE_SHIFT ) ); +} + +int +__cpu_stmode( unsigned int m ) +{ + if( m > 3 ) return( -1 ); + cctx._psw &= ~PSW_MODE_MASK; + cctx._psw |= ( (__mpu_uint32_t)m << PSW_MODE_SHIFT ) & PSW_MODE_MASK; + return( 0 ); +} + +int +__cpu_ptr_make( __mpu_uint64_t *ptr, unsigned int segment, __mpu_uint64_t offset ) +{ + if( !ptr || segment > CPU_PTR_SEG_MAX || offset > CPU_PTR_OFFSET_MASK ) return( -1 ); + *ptr = ( (__mpu_uint64_t)segment << CPU_PTR_SEG_SHIFT ) | offset; + return( 0 ); +} + +unsigned int +__cpu_ptr_segment( __mpu_uint64_t ptr ) +{ + return( (unsigned int)( ( ptr & CPU_PTR_SEG_MASK ) >> CPU_PTR_SEG_SHIFT ) ); +} + +__mpu_uint64_t +__cpu_ptr_offset( __mpu_uint64_t ptr ) +{ + return( ptr & CPU_PTR_OFFSET_MASK ); +} diff --git a/cpu/cpu-context.h b/cpu/cpu-context.h new file mode 100644 index 0000000..1957df9 --- /dev/null +++ b/cpu/cpu-context.h @@ -0,0 +1,120 @@ +/*************************************************************** + __CPU_CONTEXT.H + + This file contains CPU CONTEXT operation functions & data + declarations. + + PART OF : CPU - library . + USAGE : Internal only . + NOTE : Include "libcpu.h" before this FILE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ +#ifndef __CPU_CONTEXT_H +#define __CPU_CONTEXT_H +#ifdef __cplusplus +extern "C" { +#endif + +#define CPU_GUEST_FD_MAX 256 + +typedef struct __cpu_fd_entry +{ + int host_fd; + unsigned int owned; + unsigned int flags; +} __cpu_fd_entry; + +typedef struct __cpu_context cpu_context; +struct __cpu_context +{ + __mpu_uint64_t _pc; + __mpu_uint64_t _sp; + __mpu_uint64_t _fp; + __mpu_uint32_t _psw; + cpu_stop_reason _stop_reason; + __mpu_int32_t _exit_status; + + __mpu_UNIT_t *_program; + __mpu_uint64_t _program_size; + __mpu_uint64_t _program_capacity; + __mpu_UNIT_t *_data; + __mpu_uint64_t _data_size; + __mpu_uint64_t _data_capacity; + __mpu_UNIT_t *_stack; + __mpu_uint64_t _stack_size; + __mpu_uint64_t _stack_capacity; + __mpu_UNIT_t *_control; + __mpu_uint64_t _control_size; + __mpu_uint64_t _control_capacity; + __mpu_UNIT_t *_input; + __mpu_uint64_t _input_size; + __mpu_uint64_t _input_capacity; + __mpu_UNIT_t *_output; + __mpu_uint64_t _output_size; + __mpu_uint64_t _output_capacity; + __mpu_UNIT_t *_trace; + __mpu_uint64_t _trace_size; + __mpu_uint64_t _trace_capacity; + __mpu_UNIT_t *_heap; + __mpu_uint64_t _heap_brk; + __mpu_uint64_t _heap_size; + __mpu_uint64_t _heap_capacity; + __mpu_uint64_t _heap_limit; + + int _fd_initialized; + __cpu_fd_entry _fd[CPU_GUEST_FD_MAX]; + + int _cwd_initialized; + int _cwd_fd; + + __mpu_UNIT_t _regs[CPU_REGISTERS_MEM_SIZE_IN_UNITS]; +}; + +extern __thread struct __cpu_context *pcctx; + +#define __PC (pcctx->_pc) +#define __SP (pcctx->_sp) +#define __FP (pcctx->_fp) +#define __REGS ((__mpu_UNIT_t *)&pcctx->_regs[0]) +#define __PROGRAM (pcctx->_program) +#define __PSIZE (pcctx->_program_size) +#define __PCAP (pcctx->_program_capacity) +#define __DATA (pcctx->_data) +#define __DSIZE (pcctx->_data_size) +#define __DCAP (pcctx->_data_capacity) +#define __STACK (pcctx->_stack) +#define __SSIZE (pcctx->_stack_size) +#define __SCAP (pcctx->_stack_capacity) +#define __CONTROL (pcctx->_control) +#define __CSIZE (pcctx->_control_size) +#define __CCAP (pcctx->_control_capacity) +#define __INPUT (pcctx->_input) +#define __ISIZE (pcctx->_input_size) +#define __ICAP (pcctx->_input_capacity) +#define __OUTPUT (pcctx->_output) +#define __OSIZE (pcctx->_output_size) +#define __OCAP (pcctx->_output_capacity) +#define __TRACE (pcctx->_trace) +#define __TSIZE (pcctx->_trace_size) +#define __TCAP (pcctx->_trace_capacity) +#define __HEAP (pcctx->_heap) +#define __HBRK (pcctx->_heap_brk) +#define __HSIZE (pcctx->_heap_size) +#define __HCAP (pcctx->_heap_capacity) +#define __HLIMIT (pcctx->_heap_limit) +#define __STOP_REASON (pcctx->_stop_reason) +#define __EXIT_STATUS (pcctx->_exit_status) +#define __CPU_PSW (pcctx->_psw) +#define __SET_CFLAG(mask) (pcctx->_psw |= (mask)) +#define __CLEAR_CFLAG(mask) (pcctx->_psw &= ~(mask)) +#define __INVERT_CFLAG(mask) (pcctx->_psw ^= (mask)) +#define __CFLAG(mask,shift) ((pcctx->_psw & (mask)) >> (shift)) + +extern __mpu_uint64_t __cpu_heap_brk( __mpu_uint64_t new_brk ); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/cpu/cpu-errno.h b/cpu/cpu-errno.h new file mode 100644 index 0000000..20fb4c9 --- /dev/null +++ b/cpu/cpu-errno.h @@ -0,0 +1,160 @@ +/*************************************************************** + __CPU_ERRNO.H + + This file contains stable MCPU system-call error numbers. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_ERRNO_H +#define __CPU_ERRNO_H + +#define __MCPU_EPERM 1 /* Operation not permitted */ +#define __MCPU_ENOENT 2 /* No such file or directory */ +#define __MCPU_ESRCH 3 /* No such process */ +#define __MCPU_EINTR 4 /* Interrupted system call */ +#define __MCPU_EIO 5 /* I/O error */ +#define __MCPU_ENXIO 6 /* No such device or address */ +#define __MCPU_E2BIG 7 /* Argument list too long */ +#define __MCPU_ENOEXEC 8 /* Exec format error */ +#define __MCPU_EBADF 9 /* Bad file number */ +#define __MCPU_ECHILD 10 /* No child processes */ +#define __MCPU_EAGAIN 11 /* Try again */ +#define __MCPU_ENOMEM 12 /* Out of memory */ +#define __MCPU_EACCES 13 /* Permission denied */ +#define __MCPU_EFAULT 14 /* Bad address */ +#define __MCPU_ENOTBLK 15 /* Block device required */ +#define __MCPU_EBUSY 16 /* Device or resource busy */ +#define __MCPU_EEXIST 17 /* File exists */ +#define __MCPU_EXDEV 18 /* Cross-device link */ +#define __MCPU_ENODEV 19 /* No such device */ +#define __MCPU_ENOTDIR 20 /* Not a directory */ +#define __MCPU_EISDIR 21 /* Is a directory */ +#define __MCPU_EINVAL 22 /* Invalid argument */ +#define __MCPU_ENFILE 23 /* File table overflow */ +#define __MCPU_EMFILE 24 /* Too many open files */ +#define __MCPU_ENOTTY 25 /* Not a typewriter */ +#define __MCPU_ETXTBSY 26 /* Text file busy */ +#define __MCPU_EFBIG 27 /* File too large */ +#define __MCPU_ENOSPC 28 /* No space left on device */ +#define __MCPU_ESPIPE 29 /* Illegal seek */ +#define __MCPU_EROFS 30 /* Read-only file system */ +#define __MCPU_EMLINK 31 /* Too many links */ +#define __MCPU_EPIPE 32 /* Broken pipe */ +#define __MCPU_EDOM 33 /* Math argument out of domain */ +#define __MCPU_ERANGE 34 /* Math result not representable */ +#define __MCPU_EDEADLK 35 /* Resource deadlock would occur */ +#define __MCPU_ENAMETOOLONG 36 /* File name too long */ +#define __MCPU_ENOLCK 37 /* No record locks available */ +#define __MCPU_ENOSYS 38 /* Invalid system call number */ +#define __MCPU_ENOTEMPTY 39 /* Directory not empty */ +#define __MCPU_ELOOP 40 /* Too many symbolic links encountered */ + +#define __MCPU_EWOULDBLOCK __MCPU_EAGAIN + +#define __MCPU_ENOMSG 42 /* No message of desired type */ +#define __MCPU_EIDRM 43 /* Identifier removed */ +#define __MCPU_ECHRNG 44 /* Channel number out of range */ +#define __MCPU_EL2NSYNC 45 /* Level 2 not synchronized */ +#define __MCPU_EL3HLT 46 /* Level 3 halted */ +#define __MCPU_EL3RST 47 /* Level 3 reset */ +#define __MCPU_ELNRNG 48 /* Link number out of range */ +#define __MCPU_EUNATCH 49 /* Protocol driver not attached */ +#define __MCPU_ENOCSI 50 /* No CSI structure available */ +#define __MCPU_EL2HLT 51 /* Level 2 halted */ +#define __MCPU_EBADE 52 /* Invalid exchange */ +#define __MCPU_EBADR 53 /* Invalid request descriptor */ +#define __MCPU_EXFULL 54 /* Exchange full */ +#define __MCPU_ENOANO 55 /* No anode */ +#define __MCPU_EBADRQC 56 /* Invalid request code */ +#define __MCPU_EBADSLT 57 /* Invalid slot */ + +#define __MCPU_EDEADLOCK __MCPU_EDEADLK + +#define __MCPU_EBFONT 59 /* Bad font file format */ +#define __MCPU_ENOSTR 60 /* Device not a stream */ +#define __MCPU_ENODATA 61 /* No data available */ +#define __MCPU_ETIME 62 /* Timer expired */ +#define __MCPU_ENOSR 63 /* Out of streams resources */ +#define __MCPU_ENONET 64 /* Machine is not on the network */ +#define __MCPU_ENOPKG 65 /* Package not installed */ +#define __MCPU_EREMOTE 66 /* Object is remote */ +#define __MCPU_ENOLINK 67 /* Link has been severed */ +#define __MCPU_EADV 68 /* Advertise error */ +#define __MCPU_ESRMNT 69 /* Srmount error */ +#define __MCPU_ECOMM 70 /* Communication error on send */ +#define __MCPU_EPROTO 71 /* Protocol error */ +#define __MCPU_EMULTIHOP 72 /* Multihop attempted */ +#define __MCPU_EDOTDOT 73 /* RFS specific error */ +#define __MCPU_EBADMSG 74 /* Not a data message */ +#define __MCPU_EOVERFLOW 75 /* Value too large for defined data type */ +#define __MCPU_ENOTUNIQ 76 /* Name not unique on network */ +#define __MCPU_EBADFD 77 /* File descriptor in bad state */ +#define __MCPU_EREMCHG 78 /* Remote address changed */ +#define __MCPU_ELIBACC 79 /* Cannot access a needed shared library */ +#define __MCPU_ELIBBAD 80 /* Accessing a corrupted shared library */ +#define __MCPU_ELIBSCN 81 /* .lib section in a.out corrupted */ +#define __MCPU_ELIBMAX 82 /* Too many shared libraries */ +#define __MCPU_ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define __MCPU_EILSEQ 84 /* Illegal byte sequence */ +#define __MCPU_ERESTART 85 /* Interrupted syscall should be restarted */ +#define __MCPU_ESTRPIPE 86 /* Streams pipe error */ +#define __MCPU_EUSERS 87 /* Too many users */ +#define __MCPU_ENOTSOCK 88 /* Socket operation on non-socket */ +#define __MCPU_EDESTADDRREQ 89 /* Destination address required */ +#define __MCPU_EMSGSIZE 90 /* Message too long */ +#define __MCPU_EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define __MCPU_ENOPROTOOPT 92 /* Protocol not available */ +#define __MCPU_EPROTONOSUPPORT 93 /* Protocol not supported */ +#define __MCPU_ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define __MCPU_EOPNOTSUPP 95 /* Operation not supported */ +#define __MCPU_EPFNOSUPPORT 96 /* Protocol family not supported */ +#define __MCPU_EAFNOSUPPORT 97 /* Address family not supported */ +#define __MCPU_EADDRINUSE 98 /* Address already in use */ +#define __MCPU_EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define __MCPU_ENETDOWN 100 /* Network is down */ +#define __MCPU_ENETUNREACH 101 /* Network is unreachable */ +#define __MCPU_ENETRESET 102 /* Network dropped connection because of reset */ +#define __MCPU_ECONNABORTED 103 /* Software caused connection abort */ +#define __MCPU_ECONNRESET 104 /* Connection reset by peer */ +#define __MCPU_ENOBUFS 105 /* No buffer space available */ +#define __MCPU_EISCONN 106 /* Transport endpoint is already connected */ +#define __MCPU_ENOTCONN 107 /* Transport endpoint is not connected */ +#define __MCPU_ESHUTDOWN 108 /* Cannot send after endpoint shutdown */ +#define __MCPU_ETOOMANYREFS 109 /* Too many references */ +#define __MCPU_ETIMEDOUT 110 /* Connection timed out */ +#define __MCPU_ECONNREFUSED 111 /* Connection refused */ +#define __MCPU_EHOSTDOWN 112 /* Host is down */ +#define __MCPU_EHOSTUNREACH 113 /* No route to host */ +#define __MCPU_EALREADY 114 /* Operation already in progress */ +#define __MCPU_EINPROGRESS 115 /* Operation now in progress */ +#define __MCPU_ESTALE 116 /* Stale file handle */ +#define __MCPU_EUCLEAN 117 /* Structure needs cleaning */ +#define __MCPU_ENOTNAM 118 /* Not a XENIX named type file */ +#define __MCPU_ENAVAIL 119 /* No XENIX semaphores available */ +#define __MCPU_EISNAM 120 /* Is a named type file */ +#define __MCPU_EREMOTEIO 121 /* Remote I/O error */ +#define __MCPU_EDQUOT 122 /* Quota exceeded */ +#define __MCPU_ENOMEDIUM 123 /* No medium found */ +#define __MCPU_EMEDIUMTYPE 124 /* Wrong medium type */ +#define __MCPU_ECANCELED 125 /* Operation canceled */ +#define __MCPU_ENOKEY 126 /* Required key not available */ +#define __MCPU_EKEYEXPIRED 127 /* Key has expired */ +#define __MCPU_EKEYREVOKED 128 /* Key has been revoked */ +#define __MCPU_EKEYREJECTED 129 /* Key was rejected by service */ +#define __MCPU_EOWNERDEAD 130 /* Owner died */ +#define __MCPU_ENOTRECOVERABLE 131 /* State not recoverable */ +#define __MCPU_ERFKILL 132 /* Operation not possible due to RF-kill */ +#define __MCPU_EHWPOISON 133 /* Memory page has hardware error */ +#define __MCPU_EFTYPE 134 /* Wrong file type for intended operation */ + +#define __MCPU_ENOTSUP __MCPU_EOPNOTSUPP + +#define __MCPU_EUNKNOWN 4095 /* Unknown or unmapped HOST error */ + +#endif /* __CPU_ERRNO_H */ diff --git a/cpu/cpu-instruction.c b/cpu/cpu-instruction.c new file mode 100644 index 0000000..4a6b59e --- /dev/null +++ b/cpu/cpu-instruction.c @@ -0,0 +1,1649 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <libmcpu.h> +#include <cpu-context.h> +#include <cpu-registers.h> +#include <cpu-instruction.h> +#include <cpu-syscalls.h> + +static int +getreg( const cpu_operand *o, __mpu_UNIT_t **p, __mpu_size_t *n ) +{ + if( !o || o->type != CPU_OPERAND_REGISTER ) return( CPU_EXEC_OPERAND_FAULT ); + *n = __cpu_reg_size( o->value.reg.bits ); + if( !*n ) return( CPU_EXEC_REGISTER_FAULT ); + *p = __cpu_reg_ptr( o->value.reg.bits, o->value.reg.address ); + return( *p ? 0 : CPU_EXEC_REGISTER_FAULT ); +} + +static int +reg2( const cpu_instruction *i, __mpu_UNIT_t **d, __mpu_UNIT_t **a, __mpu_size_t *n ) +{ + __mpu_size_t nd, na; + int rc = getreg( &i->operand[0], d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], a, &na ); + if( rc ) return( rc ); + if( nd != na ) return( CPU_EXEC_WIDTH_FAULT ); + *n = nd; + return( 0 ); +} + +static int +reg3( const cpu_instruction *i, __mpu_UNIT_t **d, __mpu_UNIT_t **a, __mpu_UNIT_t **b, __mpu_size_t *n ) +{ + __mpu_size_t nd, na, nb; + int rc = getreg( &i->operand[0], d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], a, &na ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], b, &nb ); + if( rc ) return( rc ); + if( nd != na || nd != nb ) return( CPU_EXEC_WIDTH_FAULT ); + *n = nd; + return( 0 ); +} + +static int +valid_real( __mpu_size_t n ) +{ + return( n >= NB_R32 && n <= NB_R_MAX && ( n & ( n - 1 ) ) == 0 ); +} + +static int +valid_complex( __mpu_size_t n ) +{ + return( n >= 8 && n <= NB_C_MAX && ( n & ( n - 1 ) ) == 0 && valid_real( n / 2 ) ); +} + +/* + * LibMPU conversion helpers such as icvt()/r_cvt() may modify the LibMPU + * flag register as an implementation side effect. LibCPU conversion-class + * instructions are architecturally PSW-neutral, so preserve PSW[15:0]. + * + * Do this through the public LibCPU/LibMPU flag facade instead of depending + * on LibMPU's private __MPU_FLAGS/context implementation detail. + */ +static __mpu_uint32_t +cpu_status_flags_save( void ) +{ + return( __cpu_get_psw() & PSW_MPU_MASK ); +} + +static void +cpu_status_flags_restore( __mpu_uint32_t flags ) +{ + __mpu_uint32_t psw = __cpu_get_psw(); + __cpu_set_psw( ( psw & PSW_CPU_MASK ) | ( flags & PSW_MPU_MASK ) ); +} + +static int +stack_offset( __mpu_int64_t off, __mpu_uint64_t width, __mpu_uint64_t *pos ) +{ + __mpu_uint64_t p; + if( !__STACK || !__SSIZE || __SP > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + if( off < 0 ) + { + __mpu_uint64_t mag = (__mpu_uint64_t)( - ( off + 1 ) ) + 1U; + if( mag > __SP ) return( CPU_EXEC_STACK_FAULT ); + p = __SP - mag; + } + else + { + __mpu_uint64_t add = (__mpu_uint64_t)off; + if( add > __SSIZE - __SP ) return( CPU_EXEC_STACK_FAULT ); + p = __SP + add; + } + if( p > __SSIZE || width > __SSIZE - p ) return( CPU_EXEC_STACK_FAULT ); + *pos = p; + return( CPU_EXEC_SUCCESS ); +} + +static int +frame_offset( __mpu_int64_t off, __mpu_uint64_t width, __mpu_uint64_t *pos ) +{ + __mpu_uint64_t p; + if( !__STACK || !__SSIZE || __FP > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + if( off < 0 ) + { + __mpu_uint64_t mag = (__mpu_uint64_t)( - ( off + 1 ) ) + 1U; + if( mag > __FP ) return( CPU_EXEC_STACK_FAULT ); + p = __FP - mag; + } + else + { + __mpu_uint64_t add = (__mpu_uint64_t)off; + if( add > __SSIZE - __FP ) return( CPU_EXEC_STACK_FAULT ); + p = __FP + add; + } + if( p > __SSIZE || width > __SSIZE - p ) return( CPU_EXEC_STACK_FAULT ); + *pos = p; + return( CPU_EXEC_SUCCESS ); +} + +static int +pointer_register( const cpu_operand *o, __mpu_UNIT_t **p ) +{ + __mpu_size_t n; + int rc = getreg( o, p, &n ); + if( rc ) return( rc ); + return( n == CPU_PTR64_SIZE ? CPU_EXEC_SUCCESS : CPU_EXEC_WIDTH_FAULT ); +} + +static int pointer_resolve( __mpu_uint64_t ptr, __mpu_uint64_t width, int write, + __mpu_UNIT_t **base, __mpu_uint64_t *offset) +{ + unsigned int seg; + + /* ABI NULL is the all-zero PTR64. It is never dereferenceable even + though its bit fields would otherwise decode as DATA offset zero. */ + if( ptr == CPU_NULL_PTR ) return( CPU_EXEC_POINTER_FAULT ); + seg = __cpu_ptr_segment( ptr ); + __mpu_uint64_t off = __cpu_ptr_offset( ptr ), size = 0; + __mpu_UNIT_t *mem = 0; + + switch( seg ) + { + case CPU_PTR_SEG_DATA: + mem = __DATA; + size = __DSIZE; + break; + case CPU_PTR_SEG_STACK: + mem = __STACK; + size = __SSIZE; + break; + case CPU_PTR_SEG_PROGRAM: + if( write ) return( CPU_EXEC_MEMORY_FAULT ); + mem = __PROGRAM; + size = __PSIZE; + break; + case CPU_PTR_SEG_CONTROL: + if( write ) return( CPU_EXEC_MEMORY_FAULT ); + mem = __CONTROL; + size = __CSIZE; + break; + case CPU_PTR_SEG_INPUT: + if( write ) return( CPU_EXEC_MEMORY_FAULT ); + mem = __INPUT; + size = __ISIZE; + break; + case CPU_PTR_SEG_OUTPUT: + mem = __OUTPUT; + size = __OSIZE; + break; + case CPU_PTR_SEG_TRACE: + mem = __TRACE; + size = __TSIZE; + break; + case CPU_PTR_SEG_HEAP: + mem = __HEAP; + size = __HBRK; + if( off < CPU_HEAP_BEGIN ) return( CPU_EXEC_MEMORY_FAULT ); + break; + default: + return( CPU_EXEC_POINTER_FAULT ); + } + + if( !mem || off > size || width > size - off ) return( CPU_EXEC_MEMORY_FAULT ); + *base = mem; + *offset = off; + return( CPU_EXEC_SUCCESS ); +} + +/* + * Apply a signed byte displacement to a PTR64 offset while preserving the + * four-bit segment selector. The arithmetic is deliberately performed on + * the 60-bit offset field only; carry/borrow is never allowed to enter SEG. + * `subtract' selects PTR - delta instead of PTR + delta. + */ +static int pointer_arith( __mpu_uint64_t ptr, __mpu_int64_t delta, int subtract, + __mpu_uint64_t *result) +{ + __mpu_uint64_t off = __cpu_ptr_offset( ptr ); + __mpu_uint64_t mag; + unsigned int seg = __cpu_ptr_segment( ptr ); + int positive = ( delta >= 0 ); + + if( !result ) return( CPU_EXEC_POINTER_FAULT ); + + if( positive ) + mag = (__mpu_uint64_t)delta; + else + mag = (__mpu_uint64_t)( - ( delta + 1 ) ) + __mpu_UINT64_C( 1 ); + + /* PTR - negative_delta is addition; PTR + negative_delta is subtraction. */ + if( ( !subtract && positive ) || ( subtract && !positive ) ) + { + if( mag > CPU_PTR_OFFSET_MASK - off ) return( CPU_EXEC_POINTER_FAULT ); + off+=mag; + } + else + { + if( mag > off ) return( CPU_EXEC_POINTER_FAULT ); + off -= mag; + } + + if( __cpu_ptr_make( result, seg, off ) ) return( CPU_EXEC_POINTER_FAULT ); + return( CPU_EXEC_SUCCESS ); +} + +static int +reg_i64( const cpu_operand *o, __mpu_int64_t *v ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t bits; + int rc = pointer_register( o, &p ); + if( rc ) return( rc ); + bits = __cpu_data_get_u64( p ); + memcpy( v, &bits, sizeof( *v ) ); + return( CPU_EXEC_SUCCESS ); +} + +static int +reg_ptr64( const cpu_operand *o, __mpu_uint64_t *v ) +{ + __mpu_UNIT_t *p; + int rc = pointer_register( o, &p ); + if( rc ) return( rc ); + *v = __cpu_data_get_u64( p ); + return( CPU_EXEC_SUCCESS ); +} + +static int +reg_u64( const cpu_operand *o, __mpu_uint64_t *v ) +{ + __mpu_UNIT_t *p; + int rc = pointer_register( o, &p ); + if( rc ) return( rc ); + *v = __cpu_data_get_u64( p ); + return( CPU_EXEC_SUCCESS ); +} + +static int +put_i64_result( const cpu_operand *o, __mpu_int64_t v ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t bits; + int rc = pointer_register( o, &p ); + if( rc ) return( rc ); + memcpy( &bits, &v, sizeof( bits ) ); + __cpu_data_put_u64( p, bits ); + return( CPU_EXEC_SUCCESS ); +} + +static int +put_u64_result( const cpu_operand *o, __mpu_uint64_t v ) +{ + __mpu_UNIT_t *p; + int rc = pointer_register( o, &p ); + if( rc ) return( rc ); + __cpu_data_put_u64( p, v ); + return( CPU_EXEC_SUCCESS ); +} + +static int +reg_count32( const cpu_operand *o, unsigned int *count ) +{ + __mpu_uint64_t v; + int rc = reg_u64( o, &v ); + if( rc ) return( rc ); + if( v > __mpu_UINT64_C( 0xffffffff ) ) return( CPU_EXEC_OPERAND_FAULT ); + *count = (unsigned int)(__mpu_uint32_t)v; + return( CPU_EXEC_SUCCESS ); +} + +/* Resolve an indirect control-flow operand. The register must be a physical + r64/PTR64 value selecting PROGRAM, and the byte offset must address the + current architectural PROGRAM size. No host pointer is ever accepted. */ +static int +code_pointer_target( const cpu_operand *o, __mpu_uint64_t *target ) +{ + __mpu_uint64_t ptr, off; + int rc; + + if( !target ) return( CPU_EXEC_POINTER_FAULT ); + rc = reg_ptr64( o, &ptr ); + if( rc ) return( rc ); + if( ptr == CPU_NULL_PTR || __cpu_ptr_segment( ptr ) != CPU_PTR_SEG_PROGRAM ) + return( CPU_EXEC_POINTER_FAULT ); + off = __cpu_ptr_offset( ptr ); + if( !__PROGRAM || off >= __PSIZE ) return( CPU_EXEC_POINTER_FAULT ); + *target = off; + return( CPU_EXEC_SUCCESS ); +} + +static int +rconst( mpu_real *d, int nb, __mpu_uint8_t k ) +{ + switch( k ) + { +#define K(x,f) case x: f(d,nb); return 0 + K( CPU_RCONST_ZERO, _m_zero ); + K( CPU_RCONST_HALF, _m_half ); + K( CPU_RCONST_ONE, _m_one ); + K( CPU_RCONST_TWO, _m_two ); + K( CPU_RCONST_TEN, _m_ten ); + K( CPU_RCONST_MTEN, _m_mten ); + K( CPU_RCONST_32, _m_32 ); + K( CPU_RCONST_PI, _m_PI ); + K( CPU_RCONST_E, _m_E ); + K( CPU_RCONST_1_LN2, _m_1_ln2 ); + K( CPU_RCONST_LN2, _m_ln2 ); + K( CPU_RCONST_1_LN10, _m_1_ln10 ); + K( CPU_RCONST_LN10, _m_ln10 ); + K( CPU_RCONST_1_LG2, _m_1_lg2 ); + K( CPU_RCONST_LG2, _m_lg2 ); + K( CPU_RCONST_PI_2, _m_PI_2 ); + K( CPU_RCONST_PI_3, _m_PI_3 ); + K( CPU_RCONST_PI_4, _m_PI_4 ); + K( CPU_RCONST_PI_5, _m_PI_5 ); + K( CPU_RCONST_PI_6, _m_PI_6 ); + K( CPU_RCONST_1_PI, _m_1_PI ); + K( CPU_RCONST_2_PI, _m_2_PI ); + K( CPU_RCONST_3_PI, _m_3_PI ); + K( CPU_RCONST_4_PI, _m_4_PI ); + K( CPU_RCONST_5_PI, _m_5_PI ); + K( CPU_RCONST_2PI, _m_2PI ); + K( CPU_RCONST_3PI, _m_3PI ); + K( CPU_RCONST_1_2PI, _m_1_2PI ); + K( CPU_RCONST_1_3PI, _m_1_3PI ); + K( CPU_RCONST_1_4PI, _m_1_4PI ); + K( CPU_RCONST_1_5PI, _m_1_5PI ); + K( CPU_RCONST_1_6PI, _m_1_6PI ); + K( CPU_RCONST_3PI_4, _m_3PI_4 ); + K( CPU_RCONST_SQRTPI, _m_SQRTPI ); + K( CPU_RCONST_1_SQRTPI, _m_1_SQRTPI ); + K( CPU_RCONST_2_SQRTPI, _m_2_SQRTPI ); + K( CPU_RCONST_SQRT2, _m_SQRT2 ); + K( CPU_RCONST_1_SQRT2, _m_1_SQRT2 ); + K( CPU_RCONST_SQRT3, _m_SQRT3 ); + K( CPU_RCONST_1_SQRT3, _m_1_SQRT3 ); + K( CPU_RCONST_DEGREE, _m_DEGREE ); + K( CPU_RCONST_1_DEGREE, _m_1_DEGREE ); + K( CPU_RCONST_GOLDENRATIO, _m_GOLDENRATIO ); + K( CPU_RCONST_EULERGAMMA, _m_EULERGAMMA ); + K( CPU_RCONST_CATALAN, _m_CATALAN ); + K( CPU_RCONST_EPSILON, _real_epsilon ); + K( CPU_RCONST_IND, _ind ); + case CPU_RCONST_PNAN: _nan( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NNAN: _nan( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PNANMAX: _nan_max( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NNANMAX: _nan_max( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PNANMIN: _nan_min( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NNANMIN: _nan_min( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PINF: _inf( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NINF: _inf( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PREALMIN: _real_min( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NREALMIN: _real_min( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PREALMAX: _real_max( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NREALMAX: _real_max( d, 1, nb ); + return( 0 ); + case CPU_RCONST_PZERO: _signull( d, 0, nb ); + return( 0 ); + case CPU_RCONST_NZERO: _signull( d, 1, nb ); + return( 0 ); +#undef K + default: return( CPU_EXEC_OPERAND_FAULT ); + } +} + +int +__cpu_exec( const cpu_instruction *i ) +{ + __mpu_UNIT_t *d, *a, *b; + __mpu_size_t n; + int rc; + if( !i ) return( CPU_EXEC_INSTRUCTION_FAULT ); + switch( i->opcode ) + { + case CPU_OP_NOP: return( 0 ); + case CPU_OP_HALT: __cpu_sth(); + return( 0 ); + case CPU_OP_END: return( CPU_EXEC_END ); + case CPU_OP_SETF: + case CPU_OP_CLRF: + { + __mpu_uint32_t psw, mask; + if( i->operand[0].type != CPU_OPERAND_PSWBIT8 ) + return( CPU_EXEC_OPERAND_FAULT ); + if( i->operand[0].value.pswbit8 > CPU_PSWBIT_WRITE_MAX ) + return( CPU_EXEC_OPERAND_FAULT ); + psw = __cpu_get_psw(); + mask = (__mpu_uint32_t)1U << i->operand[0].value.pswbit8; + if( i->opcode == CPU_OP_SETF ) psw |= mask; + else psw &= ~mask; + __cpu_set_psw( psw ); + return( CPU_EXEC_SUCCESS ); + } + case CPU_OP_CLRCPUF: + __cpu_set_psw( __cpu_get_psw() & ~PSW_CPUF_MASK ); + return( CPU_EXEC_SUCCESS ); + case CPU_OP_CLRMPUF: + __cpu_set_psw( __cpu_get_psw() & ~PSW_MPUF_MASK ); + return( CPU_EXEC_SUCCESS ); + case CPU_OP_SYSCALL: + __cpu_data_put_u64( __cpu_reg_ptr( 64, CPU_SYSCALL_RET_ADDR ), + __cpu_syscall_dispatch() ); + return( CPU_EXEC_SUCCESS ); + case CPU_OP_EXIT: + { + __mpu_UNIT_t *p; + __mpu_size_t z; + __mpu_uint32_t u; + __mpu_int32_t status; + rc = getreg( &i->operand[0], &p, &z ); + if( rc ) return( rc ); + if( z != 4U ) return( CPU_EXEC_WIDTH_FAULT ); + u = __cpu_data_get_u32( p ); + memcpy( &status, &u, sizeof( status ) ); + __EXIT_STATUS = status; + return( CPU_EXEC_EXIT ); + } + case CPU_OP_CLR: rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + memset( d, 0, n ); + return( 0 ); + case CPU_OP_MOV: rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + memmove( d, a, n ); + return( 0 ); + case CPU_OP_SWAP: rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + { + __mpu_size_t k; + for( k = 0; k < n; k++ ) + { + __mpu_UNIT_t t = d[k]; + d[k] = a[k]; + a[k] = t; + } + } + return( 0 ); + case CPU_OP_BSWAP: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + { + __mpu_size_t k; + /* Equal-width naturally aligned register views are either identical or + disjoint. These two paths are therefore exactly equivalent to the + architectural source-snapshot semantics without an 8 KiB temp. */ + if( d == a ) + { + for( k = 0; k < n / 2U; k++ ) + { + __mpu_UNIT_t t = d[k]; + d[k] = d[n - 1U - k]; + d[n - 1U - k] = t; + } + } + else + { + for( k = 0; k < n; k++ ) d[k] = a[n - 1U - k]; + } + } + return( 0 ); + case CPU_OP_LDI: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( i->operand[1].type != CPU_OPERAND_IMM64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + mpu_int64_t s; + __mpu_int64_t v = (__mpu_int64_t)i->operand[1].value.imm64; + __mpu_uint32_t flags = cpu_status_flags_save(); + memcpy( s, &v, 8 ); + icvt( ( mpu_int * ) d, ( mpu_int * ) s, (int)n, NB_I64 ); + cpu_status_flags_restore( flags ); + } + return( 0 ); + case CPU_OP_LDR: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( !valid_real( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_IMM64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + mpu_real64_t s; + __mpu_uint64_t u = i->operand[1].value.imm64; + __mpu_real64_t v; + __mpu_uint32_t flags = cpu_status_flags_save(); + memcpy( &v, &u, 8 ); + memcpy( s, &v, 8 ); + r_cvt( ( mpu_real * ) d, ( mpu_real * ) s, (int)n, NB_R64 ); + cpu_status_flags_restore( flags ); + } + return( 0 ); + case CPU_OP_LDC: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( !valid_complex( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_IMM64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + mpu_real32_t sr, si; + mpu_wide_real_t rr, ri; + __mpu_uint32_t ur = (__mpu_uint32_t)( i->operand[1].value.imm64 >> 32 ), ui = (__mpu_uint32_t)i->operand[1].value.imm64; + __mpu_real32_t fr, fi; + int cn = (int)( n / 2 ); + __mpu_uint32_t flags = cpu_status_flags_save(); + memcpy( &fr, &ur, 4 ); + memcpy( &fi, &ui, 4 ); + memcpy( sr, &fr, 4 ); + memcpy( si, &fi, 4 ); + r_cvt( ( mpu_real * ) rr, ( mpu_real * ) sr, cn, NB_R32 ); + r_cvt( ( mpu_real * ) ri, ( mpu_real * ) si, cn, NB_R32 ); + c_gen_complex( ( mpu_complex * ) d, ( mpu_real * ) rr, ( mpu_real * ) ri, cn ); + cpu_status_flags_restore( flags ); + } + return( 0 ); + case CPU_OP_LOAD: + case CPU_OP_STORE: + { + const cpu_operand *ro = ( i->opcode == CPU_OP_LOAD ) ? &i->operand[0] : &i->operand[1]; + const cpu_operand *ao = ( i->opcode == CPU_OP_LOAD ) ? &i->operand[1] : &i->operand[0]; + rc = getreg( ro, &d, &n ); + if( rc ) return( rc ); + if( ao->type != CPU_OPERAND_ADDR64 ) return( CPU_EXEC_OPERAND_FAULT ); + if( ao->value.addr64 > __DSIZE || (__mpu_uint64_t)n > __DSIZE - ao->value.addr64 ) return( CPU_EXEC_MEMORY_FAULT ); + if( i->opcode == CPU_OP_LOAD ) memmove( d, __DATA + ao->value.addr64, n ); + else memmove( __DATA + ao->value.addr64, d, n ); + return( 0 ); + } + + case CPU_OP_LEA: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( n != CPU_PTR64_SIZE ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_PTR64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t ptr = i->operand[1].value.ptr64, off = __cpu_ptr_offset( ptr ), size = 0; + unsigned int seg = __cpu_ptr_segment( ptr ); + switch( seg ) + { + case CPU_PTR_SEG_DATA: size = __DSIZE; + break; + case CPU_PTR_SEG_STACK: size = __SSIZE; + break; + case CPU_PTR_SEG_PROGRAM: size = __PSIZE; + break; + case CPU_PTR_SEG_CONTROL: size = __CSIZE; + break; + case CPU_PTR_SEG_INPUT: size = __ISIZE; + break; + case CPU_PTR_SEG_OUTPUT: size = __OSIZE; + break; + case CPU_PTR_SEG_TRACE: size = __TSIZE; + break; + case CPU_PTR_SEG_HEAP: size = __HBRK; + if( off < CPU_HEAP_BEGIN ) return( CPU_EXEC_MEMORY_FAULT ); + break; + default: return( CPU_EXEC_POINTER_FAULT ); + } + if( off > size ) return( CPU_EXEC_MEMORY_FAULT ); + __cpu_data_put_u64( d, ptr ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_LEASP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( n != CPU_PTR64_SIZE ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t pos, ptr; + rc = stack_offset( i->operand[1].value.off64, 0, &pos ); + if( rc ) return( rc ); + if( pos > CPU_PTR_OFFSET_MASK ) return( CPU_EXEC_POINTER_FAULT ); + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, pos ) ) return( CPU_EXEC_POINTER_FAULT ); + __cpu_data_put_u64( d, ptr ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_LOADP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + rc = pointer_register( &i->operand[1], &a ); + if( rc ) return( rc ); + { + __mpu_uint64_t ptr, off; + __mpu_UNIT_t *base; + ptr = __cpu_data_get_u64( a ); + rc = pointer_resolve( ptr, (__mpu_uint64_t)n, 0, &base, &off ); + if( rc ) return( rc ); + memmove( d, base + off, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_STOREP: + rc = pointer_register( &i->operand[0], &a ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &d, &n ); + if( rc ) return( rc ); + { + __mpu_uint64_t ptr, off; + __mpu_UNIT_t *base; + ptr = __cpu_data_get_u64( a ); + rc = pointer_resolve( ptr, (__mpu_uint64_t)n, 1, &base, &off ); + if( rc ) return( rc ); + memmove( base + off, d, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_BMOV: + case CPU_OP_BMOVI: + { + __mpu_uint64_t dstptr, srcptr, count, dstoff, srcoff; + __mpu_UNIT_t *dstbase, *srcbase; + rc = reg_ptr64( &i->operand[0], &dstptr ); + if( rc ) return( rc ); + rc = reg_ptr64( &i->operand[1], &srcptr ); + if( rc ) return( rc ); + if( i->opcode == CPU_OP_BMOV ) + { + rc = reg_u64( &i->operand[2], &count ); + if( rc ) return( rc ); + } + else + { + if( i->operand[2].type != CPU_OPERAND_IMM64 ) return( CPU_EXEC_OPERAND_FAULT ); + count = i->operand[2].value.imm64; + } + /* Resolve and validate both complete ranges before modifying memory. */ + rc = pointer_resolve( srcptr, count, 0, &srcbase, &srcoff ); + if( rc ) return( rc ); + rc = pointer_resolve( dstptr, count, 1, &dstbase, &dstoff ); + if( rc ) return( rc ); + if( (__mpu_uint64_t)(size_t)count != count ) return( CPU_EXEC_MEMORY_FAULT ); + memmove( dstbase + dstoff, srcbase + srcoff, (size_t)count ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_PADD: + case CPU_OP_PSUB: + { + __mpu_uint64_t ptr, out; + __mpu_int64_t delta; + rc = pointer_register( &i->operand[0], &d ); + if( rc ) return( rc ); + rc = reg_ptr64( &i->operand[1], &ptr ); + if( rc ) return( rc ); + rc = reg_i64( &i->operand[2], &delta ); + if( rc ) return( rc ); + rc = pointer_arith( ptr, delta, i->opcode == CPU_OP_PSUB, &out ); + if( rc ) return( rc ); + __cpu_data_put_u64( d, out ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_PADDI: + case CPU_OP_PSUBI: + { + __mpu_uint64_t ptr, out; + __mpu_int64_t delta; + rc = pointer_register( &i->operand[0], &d ); + if( rc ) return( rc ); + rc = reg_ptr64( &i->operand[1], &ptr ); + if( rc ) return( rc ); + if( i->operand[2].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + delta = i->operand[2].value.off64; + rc = pointer_arith( ptr, delta, i->opcode == CPU_OP_PSUBI, &out ); + if( rc ) return( rc ); + __cpu_data_put_u64( d, out ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_PDIFF: + { + __mpu_uint64_t left, right; + __mpu_int64_t diff; + rc = pointer_register( &i->operand[0], &d ); + if( rc ) return( rc ); + rc = reg_ptr64( &i->operand[1], &left ); + if( rc ) return( rc ); + rc = reg_ptr64( &i->operand[2], &right ); + if( rc ) return( rc ); + if( __cpu_ptr_segment( left ) != __cpu_ptr_segment( right ) ) + return( CPU_EXEC_POINTER_FAULT ); + diff = (__mpu_int64_t)__cpu_ptr_offset( left ) - + (__mpu_int64_t)__cpu_ptr_offset( right ); + { + __mpu_uint64_t bits; + memcpy( &bits, &diff, sizeof( bits ) ); + __cpu_data_put_u64( d, bits ); + } + } + return( CPU_EXEC_SUCCESS ); + +#define I3(op,fn) case op: rc=reg3(i,&d,&a,&b,&n);if(rc)return rc;fn((mpu_int*)d,(mpu_int*)a,(mpu_int*)b,(int)n);return 0 + I3( CPU_OP_IADD, iadd ); + I3( CPU_OP_IADC, iadc ); + I3( CPU_OP_ISUB, isub ); + I3( CPU_OP_ISBB, isbb ); + I3( CPU_OP_IAND, iand ); + I3( CPU_OP_IOR, ior ); + I3( CPU_OP_IXOR, ixor ); +#undef I3 +#define I2(op,fn) case op: rc=reg2(i,&d,&a,&n);if(rc)return rc;fn((mpu_int*)d,(mpu_int*)a,(int)n);return 0 + I2( CPU_OP_INOT, inot ); + I2( CPU_OP_INEG, ineg ); + I2( CPU_OP_ISHL, ishl ); + I2( CPU_OP_ISHR, ishr ); + I2( CPU_OP_ISAL, isal ); + I2( CPU_OP_ISAR, isar ); + I2( CPU_OP_IROL, irol ); + I2( CPU_OP_IROR, iror ); + I2( CPU_OP_IRCL, ircl ); + I2( CPU_OP_IRCR, ircr ); + I2( CPU_OP_IINC, iinc ); + I2( CPU_OP_IDEC, idec ); +#undef I2 + case CPU_OP_IXCHG: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + ixchg( ( mpu_int * ) d, ( mpu_int * ) a, (int)n ); + return( 0 ); + case CPU_OP_ICPY: + case CPU_OP_ICVT: + { + __mpu_size_t nd, na; + rc = getreg( &i->operand[0], &d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + if( i->opcode == CPU_OP_ICPY ) icpy( ( mpu_int * ) d, ( mpu_int * ) a, (int)nd, (int)na ); + else + { + __mpu_uint32_t flags = cpu_status_flags_save(); + icvt( ( mpu_int * ) d, ( mpu_int * ) a, (int)nd, (int)na ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_ISHLN: case CPU_OP_ISHRN: case CPU_OP_ISALN: case CPU_OP_ISARN: + case CPU_OP_IROLN: case CPU_OP_IRORN: case CPU_OP_IRCLN: case CPU_OP_IRCRN: + { + __mpu_size_t nd, na; + unsigned int count; + rc = getreg( &i->operand[0], &d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + if( nd != na ) return( CPU_EXEC_WIDTH_FAULT ); + rc = reg_count32( &i->operand[2], &count ); + if( rc ) return( rc ); + switch( i->opcode ) + { + case CPU_OP_ISHLN: ishln( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_ISHRN: ishrn( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_ISALN: isaln( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_ISARN: isarn( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_IROLN: iroln( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_IRORN: irorn( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + case CPU_OP_IRCLN: ircln( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + default: ircrn( ( mpu_int * ) d, ( mpu_int * ) a, count, (int)nd ); + break; + } + } + return( 0 ); + case CPU_OP_IMUL: + case CPU_OP_ISMUL: + { + __mpu_size_t np, na, nb; + rc = getreg( &i->operand[0], &d, &np ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &b, &nb ); + if( rc ) return( rc ); + if( na != nb || np < 2U * na ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->opcode == CPU_OP_IMUL ) imul( ( mpu_int * ) d, ( mpu_int * ) a, ( mpu_int * ) b, (int)np, (int)na ); + else ismul( ( mpu_int * ) d, ( mpu_int * ) a, ( mpu_int * ) b, (int)np, (int)na ); + } + return( 0 ); + case CPU_OP_IDIV: + case CPU_OP_ISDIV: + { + __mpu_UNIT_t *q, *r, *num, *den; + __mpu_size_t nq, nr, nn, nd; + rc = getreg( &i->operand[0], &q, &nq ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &r, &nr ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &num, &nn ); + if( rc ) return( rc ); + rc = getreg( &i->operand[3], &den, &nd ); + if( rc ) return( rc ); + if( nq != nr || nq != nn || nq != nd ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->opcode == CPU_OP_IDIV ) idiv( ( mpu_int * ) q, ( mpu_int * ) r, ( mpu_int * ) num, ( mpu_int * ) den, (int)nq ); + else isdiv( ( mpu_int * ) q, ( mpu_int * ) r, ( mpu_int * ) num, ( mpu_int * ) den, (int)nq ); + } + return( 0 ); + case CPU_OP_IBITSLEN: + { + __mpu_size_t na; + int v; + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + v = ibits_len( ( mpu_int * ) a, (int)na ); + return( put_i64_result( &i->operand[0], (__mpu_int64_t)v ) ); + } + case CPU_OP_ICMP: case CPU_OP_ITEST: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + if( i->opcode == CPU_OP_ICMP ) icmp( ( mpu_int * ) d, ( mpu_int * ) a, (int)n ); + else itest( ( mpu_int * ) d, ( mpu_int * ) a, (int)n ); + return( 0 ); + +#define R3(op,fn,limit) case op: rc=reg3(i,&d,&a,&b,&n);if(rc)return rc;if(!valid_real(n)||n>(limit))return CPU_EXEC_WIDTH_FAULT;fn((mpu_real*)d,(mpu_real*)a,(mpu_real*)b,(int)n);return 0 + R3( CPU_OP_RADD, r_add, NB_R_MAX ); + R3( CPU_OP_RSUB, r_sub, NB_R_MAX ); + R3( CPU_OP_RMUL, r_mul, NB_R_MAX ); + R3( CPU_OP_RDIV, r_div, NB_R_MAX ); + R3( CPU_OP_RPOW, r_pow, NB_R16384 ); + R3( CPU_OP_RHYPOT, r_hypot, NB_R16384 ); + R3( CPU_OP_RATAN2, r_atan2, NB_R16384 ); + R3( CPU_OP_RREMAIN, r_remain, NB_R_MAX ); +#undef R3 +#define R2(op,fn,limit) case op: rc=reg2(i,&d,&a,&n);if(rc)return rc;if(!valid_real(n)||n>(limit))return CPU_EXEC_WIDTH_FAULT;fn((mpu_real*)d,(mpu_real*)a,(int)n);return 0 + R2( CPU_OP_RSQRT, r_sqrt, NB_R16384 ); + R2( CPU_OP_RSIN, r_sin, NB_R16384 ); + R2( CPU_OP_RCOS, r_cos, NB_R16384 ); + R2( CPU_OP_RTAN, r_tan, NB_R16384 ); + R2( CPU_OP_RASIN, r_asin, NB_R16384 ); + R2( CPU_OP_RACOS, r_acos, NB_R16384 ); + R2( CPU_OP_RATAN, r_atan, NB_R16384 ); + R2( CPU_OP_RLOG, r_log, NB_R16384 ); + R2( CPU_OP_RLOG10, r_log10, NB_R16384 ); + R2( CPU_OP_RLOG2, r_log2, NB_R16384 ); + R2( CPU_OP_REXP, r_exp, NB_R16384 ); + R2( CPU_OP_RSINH, r_sinh, NB_R16384 ); + R2( CPU_OP_RCOSH, r_cosh, NB_R16384 ); + R2( CPU_OP_RTANH, r_tanh, NB_R16384 ); + R2( CPU_OP_RFLOOR, r_floor, NB_R_MAX ); + R2( CPU_OP_RCEIL, r_ceil, NB_R_MAX ); + R2( CPU_OP_RROUND, r_round, NB_R_MAX ); + R2( CPU_OP_RLOG1P, r_log1p, NB_R16384 ); + R2( CPU_OP_REXPM1, r_expm1, NB_R16384 ); + R2( CPU_OP_RASINH, r_asinh, NB_R16384 ); + R2( CPU_OP_RACOSH, r_acosh, NB_R16384 ); + R2( CPU_OP_RATANH, r_atanh, NB_R16384 ); +#undef R2 + case CPU_OP_RNEG: + case CPU_OP_RABS: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + if( !valid_real( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + if( d != a ) r_cpy( ( mpu_real * ) d, ( mpu_real * ) a, (int)n ); + if( i->opcode == CPU_OP_RNEG ) r_neg( ( mpu_real * ) d, (int)n ); + else r_abs( ( mpu_real * ) d, (int)n ); + return( 0 ); + case CPU_OP_RCPY: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + if( !valid_real( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + r_cpy( ( mpu_real * ) d, ( mpu_real * ) a, (int)n ); + return( 0 ); + case CPU_OP_RCVT: + { + __mpu_size_t nd, na; + rc = getreg( &i->operand[0], &d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + if( !valid_real( nd ) || !valid_real( na ) ) return( CPU_EXEC_WIDTH_FAULT ); + { + __mpu_uint32_t flags = cpu_status_flags_save(); + r_cvt( ( mpu_real * ) d, ( mpu_real * ) a, (int)nd, (int)na ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_RCMP: + { + __mpu_size_t na, nb; + int v; + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &b, &nb ); + if( rc ) return( rc ); + if( na != nb || !valid_real( na ) ) return( CPU_EXEC_WIDTH_FAULT ); + v = r_cmp( ( mpu_real * ) a, ( mpu_real * ) b, (int)na ); + return( put_i64_result( &i->operand[0], (__mpu_int64_t)v ) ); + } + case CPU_OP_RFREXP: + { + __mpu_UNIT_t *sig, *exp, *src; + __mpu_size_t nsig, nexp, nsrc; + rc = getreg( &i->operand[0], &sig, &nsig ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &exp, &nexp ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &src, &nsrc ); + if( rc ) return( rc ); + if( nsig != nsrc || !valid_real( nsig ) ) return( CPU_EXEC_WIDTH_FAULT ); + r_frexp( ( mpu_real * ) sig, ( mpu_int * ) exp, ( mpu_real * ) src, (int)nexp, (int)nsrc ); + } + return( 0 ); + case CPU_OP_RLDEXP: + { + __mpu_UNIT_t *dst, *src, *exp; + __mpu_size_t ndst, nsrc, nexp; + rc = getreg( &i->operand[0], &dst, &ndst ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &src, &nsrc ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &exp, &nexp ); + if( rc ) return( rc ); + if( ndst != nsrc || !valid_real( ndst ) ) return( CPU_EXEC_WIDTH_FAULT ); + r_ldexp( ( mpu_real * ) dst, ( mpu_real * ) src, ( mpu_int * ) exp, (int)ndst, (int)nexp ); + } + return( 0 ); + case CPU_OP_RLOGB: + { + __mpu_UNIT_t *dst, *src; + __mpu_size_t ndst, nsrc; + rc = getreg( &i->operand[0], &dst, &ndst ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &src, &nsrc ); + if( rc ) return( rc ); + if( !valid_real( nsrc ) ) return( CPU_EXEC_WIDTH_FAULT ); + r_logb( ( mpu_int * ) dst, ( mpu_real * ) src, (int)ndst, (int)nsrc ); + } + return( 0 ); + case CPU_OP_RMODF: + { + __mpu_UNIT_t *frac, *integ, *src; + __mpu_size_t nf, ni, ns; + rc = getreg( &i->operand[0], &frac, &nf ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &integ, &ni ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &src, &ns ); + if( rc ) return( rc ); + if( nf != ni || nf != ns || !valid_real( nf ) ) return( CPU_EXEC_WIDTH_FAULT ); + r_modf( ( mpu_real * ) frac, ( mpu_real * ) integ, ( mpu_real * ) src, (int)nf ); + } + return( 0 ); + case CPU_OP_ITOR: + case CPU_OP_UITOR: + { + __mpu_UNIT_t *rdst, *isrc; + __mpu_size_t nr, ni; + rc = getreg( &i->operand[0], &rdst, &nr ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &isrc, &ni ); + if( rc ) return( rc ); + if( !valid_real( nr ) ) return( CPU_EXEC_WIDTH_FAULT ); + { + __mpu_uint32_t flags = cpu_status_flags_save(); + if( i->opcode == CPU_OP_ITOR ) ltor( ( mpu_real * ) rdst, ( mpu_int * ) isrc, (int)nr, (int)ni ); + else ultor( ( mpu_real * ) rdst, ( mpu_int * ) isrc, (int)nr, (int)ni ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_RTOI: + case CPU_OP_RTOUI: + { + __mpu_UNIT_t *idst, *frac, *src; + __mpu_size_t ni, nf, ns; + rc = getreg( &i->operand[0], &idst, &ni ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &frac, &nf ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &src, &ns ); + if( rc ) return( rc ); + if( nf != ns || !valid_real( nf ) ) return( CPU_EXEC_WIDTH_FAULT ); + { + __mpu_uint32_t flags = cpu_status_flags_save(); + if( i->opcode == CPU_OP_RTOI ) rtol_frac( ( mpu_int * ) idst, ( mpu_real * ) frac, ( mpu_real * ) src, (int)ni, (int)ns ); + else rtoul_frac( ( mpu_int * ) idst, ( mpu_real * ) frac, ( mpu_real * ) src, (int)ni, (int)ns ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_RISNEG: + case CPU_OP_RSIGN: + case CPU_OP_RISIND: + case CPU_OP_RISNAN: + case CPU_OP_RISNANMAX: + case CPU_OP_RISNANMIN: + case CPU_OP_RISINF: + case CPU_OP_RISSIGNULL: + { + __mpu_size_t ns; + int v; + rc = getreg( &i->operand[1], &a, &ns ); + if( rc ) return( rc ); + if( !valid_real( ns ) ) return( CPU_EXEC_WIDTH_FAULT ); + switch( i->opcode ) + { + case CPU_OP_RISNEG: v = r_is_neg( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RSIGN: v = r_sign( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RISIND: v = _is_ind( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RISNAN: v = _is_nans( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RISNANMAX: v = _is_nan_max( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RISNANMIN: v = _is_nan_min( ( mpu_real * ) a, (int)ns ); + break; + case CPU_OP_RISINF: v = _is_inf( ( mpu_real * ) a, (int)ns ); + break; + default: v = _is_signull( ( mpu_real * ) a, (int)ns ); + break; + } + return( put_i64_result( &i->operand[0], (__mpu_int64_t)v ) ); + } + case CPU_OP_RCONST: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( !valid_real( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_CONST8 ) return( CPU_EXEC_OPERAND_FAULT ); + return( rconst( ( mpu_real * ) d, (int)n, i->operand[1].value.const8 ) ); + +#define C3(op,fn,limit) case op: rc=reg3(i,&d,&a,&b,&n);if(rc)return rc;if(!valid_complex(n)||n>(limit))return CPU_EXEC_WIDTH_FAULT;fn((mpu_complex*)d,(mpu_complex*)a,(mpu_complex*)b,(int)(n/2));return 0 + C3( CPU_OP_CADD, c_add, NB_C_MAX ); + C3( CPU_OP_CSUB, c_sub, NB_C_MAX ); + C3( CPU_OP_CMUL, c_mul, NB_C_MAX ); + C3( CPU_OP_CDIV, c_div, NB_C_MAX ); + C3( CPU_OP_CPOW, c_pow, NB_C32768 ); +#undef C3 +#define C2(op,fn) case op: rc=reg2(i,&d,&a,&n);if(rc)return rc;if(!valid_complex(n)||n>NB_C32768)return CPU_EXEC_WIDTH_FAULT;fn((mpu_complex*)d,(mpu_complex*)a,(int)(n/2));return 0 + C2( CPU_OP_CEXP, c_exp ); + C2( CPU_OP_CSIN, c_sin ); + C2( CPU_OP_CCOS, c_cos ); + C2( CPU_OP_CSINH, c_sinh ); + C2( CPU_OP_CCOSH, c_cosh ); + C2( CPU_OP_CLOG, c_log ); + C2( CPU_OP_CSQRT, c_sqrt ); +#undef C2 + case CPU_OP_CCONJ: + case CPU_OP_CCPY: + rc = reg2( i, &d, &a, &n ); + if( rc ) return( rc ); + if( !valid_complex( n ) ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->opcode == CPU_OP_CCONJ ) c_conj( ( mpu_complex * ) d, ( mpu_complex * ) a, (int)( n / 2 ) ); + else c_cpy( ( mpu_complex * ) d, ( mpu_complex * ) a, (int)( n / 2 ) ); + return( 0 ); + case CPU_OP_CCVT: + { + __mpu_size_t nd, ns; + rc = getreg( &i->operand[0], &d, &nd ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &ns ); + if( rc ) return( rc ); + if( !valid_complex( nd ) || !valid_complex( ns ) ) return( CPU_EXEC_WIDTH_FAULT ); + { + __mpu_uint32_t flags = cpu_status_flags_save(); + c_cvt( ( mpu_complex * ) d, ( mpu_complex * ) a, (int)( nd / 2 ), (int)( ns / 2 ) ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_CREAL: + case CPU_OP_CIMAG: + case CPU_OP_CABS: + case CPU_OP_CNORM: + case CPU_OP_CARG: + { + __mpu_size_t nr, nc; + rc = getreg( &i->operand[0], &d, &nr ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &nc ); + if( rc ) return( rc ); + if( !valid_real( nr ) || !valid_complex( nc ) || nc != 2U * nr ) return( CPU_EXEC_WIDTH_FAULT ); + if( ( i->opcode == CPU_OP_CABS || i->opcode == CPU_OP_CARG ) && nr > NB_R16384 ) + return( CPU_EXEC_WIDTH_FAULT ); + switch( i->opcode ) + { + case CPU_OP_CREAL: c_real_part( ( mpu_real * ) d, ( mpu_complex * ) a, (int)nr ); + break; + case CPU_OP_CIMAG: c_imaginary( ( mpu_real * ) d, ( mpu_complex * ) a, (int)nr ); + break; + case CPU_OP_CABS: c_abs( ( mpu_real * ) d, ( mpu_complex * ) a, (int)nr ); + break; + case CPU_OP_CNORM: c_norm( ( mpu_real * ) d, ( mpu_complex * ) a, (int)nr ); + break; + default: c_arg( ( mpu_real * ) d, ( mpu_complex * ) a, (int)nr ); + break; + } + } + return( 0 ); + case CPU_OP_CRTOC: + { + __mpu_size_t nc, nr; + rc = getreg( &i->operand[0], &d, &nc ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &nr ); + if( rc ) return( rc ); + if( !valid_complex( nc ) || !valid_real( nr ) || nc != 2U * nr ) return( CPU_EXEC_WIDTH_FAULT ); + { + __mpu_uint32_t flags = cpu_status_flags_save(); + c_real_to_complex( ( mpu_complex * ) d, ( mpu_real * ) a, (int)nr ); + cpu_status_flags_restore( flags ); + } + } + return( 0 ); + case CPU_OP_CGEN: + case CPU_OP_CPOLAR: + { + __mpu_size_t nc, nr, ni; + rc = getreg( &i->operand[0], &d, &nc ); + if( rc ) return( rc ); + rc = getreg( &i->operand[1], &a, &nr ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &b, &ni ); + if( rc ) return( rc ); + if( nr != ni || !valid_real( nr ) || !valid_complex( nc ) || nc != 2U * nr ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->opcode == CPU_OP_CPOLAR && nr > NB_R16384 ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->opcode == CPU_OP_CGEN ) c_gen_complex( ( mpu_complex * ) d, ( mpu_real * ) a, ( mpu_real * ) b, (int)nr ); + else c_polar( ( mpu_complex * ) d, ( mpu_real * ) a, ( mpu_real * ) b, (int)nr ); + } + return( 0 ); + case CPU_OP_CISEQ: + case CPU_OP_CISNE: + { + __mpu_size_t na, nb; + int v; + rc = getreg( &i->operand[1], &a, &na ); + if( rc ) return( rc ); + rc = getreg( &i->operand[2], &b, &nb ); + if( rc ) return( rc ); + if( na != nb || !valid_complex( na ) ) return( CPU_EXEC_WIDTH_FAULT ); + v = ( i->opcode == CPU_OP_CISEQ ) ? + c_is_equal( ( mpu_complex * ) a, ( mpu_complex * ) b, (int)( na / 2 ) ): + c_is_nequal( ( mpu_complex * ) a, ( mpu_complex * ) b, (int)( na / 2 ) ); + return( put_i64_result( &i->operand[0], (__mpu_int64_t)v ) ); + } + + case CPU_OP_JMP: + if( i->operand[0].type != CPU_OPERAND_CODE64 ) return( CPU_EXEC_OPERAND_FAULT ); + if( i->operand[0].value.code64 >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + __PC = i->operand[0].value.code64; + return( CPU_EXEC_BRANCH ); + + case CPU_OP_JMPP: + { + __mpu_uint64_t target; + rc = code_pointer_target( &i->operand[0], &target ); + if( rc ) return( rc ); + __PC = target; + } + return( CPU_EXEC_BRANCH ); + + case CPU_OP_JSET: + case CPU_OP_JCLR: + if( i->operand[0].type != CPU_OPERAND_PSWBIT8 || + i->operand[1].type != CPU_OPERAND_CODE64) + return( CPU_EXEC_OPERAND_FAULT ); + if( i->operand[0].value.pswbit8 > CPU_PSWBIT_MAX ) + return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint32_t mask = (__mpu_uint32_t)1U << i->operand[0].value.pswbit8; + int set = ( ( __cpu_get_psw() & mask ) != 0 ); + int take = ( i->opcode == CPU_OP_JSET ) ? set : !set; + if( take ) + { + if( i->operand[1].value.code64 >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + __PC = i->operand[1].value.code64; + return( CPU_EXEC_BRANCH ); + } + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_CALL: + if( i->operand[0].type != CPU_OPERAND_CODE64 ) return( CPU_EXEC_OPERAND_FAULT ); + if( i->operand[0].value.code64 >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + if( __PC > __PSIZE || ( CPU_OPCODE_SIZE + CPU_CODE64_SIZE ) > __PSIZE - __PC ) return( CPU_EXEC_MEMORY_FAULT ); + { + __mpu_uint64_t ret = __PC + CPU_OPCODE_SIZE + CPU_CODE64_SIZE; + __mpu_uint64_t p; + if( ret >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + if( !__STACK || !__SSIZE || __SP > __SSIZE || CPU_CODE64_SIZE > __SP ) return( CPU_EXEC_STACK_FAULT ); + p = __SP - CPU_CODE64_SIZE; + __cpu_data_put_u64( __STACK + p, ret ); + __SP = p; + __PC = i->operand[0].value.code64; + } + return( CPU_EXEC_BRANCH ); + + case CPU_OP_CALLP: + { + const __mpu_uint64_t ilen = CPU_OPCODE_SIZE + CPU_REG24_SIZE; + __mpu_uint64_t target, ret, p; + + /* Validate every source of failure before touching STACK, SP or PC. */ + rc = code_pointer_target( &i->operand[0], &target ); + if( rc ) return( rc ); + if( __PC > __PSIZE || ilen > __PSIZE - __PC ) return( CPU_EXEC_MEMORY_FAULT ); + ret = __PC + ilen; + if( ret >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + if( !__STACK || !__SSIZE || __SP > __SSIZE || CPU_CODE64_SIZE > __SP ) + return( CPU_EXEC_STACK_FAULT ); + + p = __SP - CPU_CODE64_SIZE; + __cpu_data_put_u64( __STACK + p, ret ); + __SP = p; + __PC = target; + } + return( CPU_EXEC_BRANCH ); + + case CPU_OP_RET: + { + __mpu_uint64_t target; + if( !__STACK || !__SSIZE || __SP > __SSIZE || CPU_CODE64_SIZE > __SSIZE - __SP ) return( CPU_EXEC_STACK_FAULT ); + target = __cpu_data_get_u64( __STACK + __SP ); + if( target >= __PSIZE ) return( CPU_EXEC_MEMORY_FAULT ); + __SP+=CPU_CODE64_SIZE; + __PC = target; + } + return( CPU_EXEC_BRANCH ); + + case CPU_OP_PUSH: + rc = getreg( &i->operand[0], &a, &n ); + if( rc ) return( rc ); + if( !__STACK || !__SSIZE || __SP > __SSIZE || (__mpu_uint64_t)n > __SP ) return( CPU_EXEC_STACK_FAULT ); + { + __mpu_uint64_t p = __SP - (__mpu_uint64_t)n; + memmove( __STACK + p, a, n ); + __SP = p; + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_POP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( !__STACK || !__SSIZE || __SP > __SSIZE || (__mpu_uint64_t)n > __SSIZE - __SP ) return( CPU_EXEC_STACK_FAULT ); + memmove( d, __STACK + __SP, n ); + __SP += (__mpu_uint64_t)n; + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_LDSP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( i->operand[1].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t p; + rc = stack_offset( i->operand[1].value.off64, (__mpu_uint64_t)n, &p ); + if( rc ) return( rc ); + memmove( d, __STACK + p, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_STSP: + if( i->operand[0].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + rc = getreg( &i->operand[1], &a, &n ); + if( rc ) return( rc ); + { + __mpu_uint64_t p; + rc = stack_offset( i->operand[0].value.off64, (__mpu_uint64_t)n, &p ); + if( rc ) return( rc ); + memmove( __STACK + p, a, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_ADJSP: + if( i->operand[0].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t p; + rc = stack_offset( i->operand[0].value.off64, 0, &p ); + if( rc ) return( rc ); + __SP = p; + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_PUSHFP: + if( !__STACK || !__SSIZE || __SP > __SSIZE || __FP > __SSIZE || CPU_PTR64_SIZE > __SP ) + return( CPU_EXEC_STACK_FAULT ); + { + __mpu_uint64_t p = __SP - CPU_PTR64_SIZE; + __cpu_data_put_u64( __STACK + p, __FP ); + __SP = p; + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_SETFP: + if( !__STACK || !__SSIZE || __SP > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + __FP = __SP; + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_RESTSP: + if( !__STACK || !__SSIZE || __FP > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + __SP = __FP; + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_POPFP: + { + __mpu_uint64_t oldfp; + if( !__STACK || !__SSIZE || __SP > __SSIZE || CPU_PTR64_SIZE > __SSIZE - __SP ) + return( CPU_EXEC_STACK_FAULT ); + oldfp = __cpu_data_get_u64( __STACK + __SP ); + if( oldfp > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + __FP = oldfp; + __SP+=CPU_PTR64_SIZE; + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_LDFP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( i->operand[1].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t p; + rc = frame_offset( i->operand[1].value.off64, (__mpu_uint64_t)n, &p ); + if( rc ) return( rc ); + memmove( d, __STACK + p, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_STFP: + if( i->operand[0].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + rc = getreg( &i->operand[1], &a, &n ); + if( rc ) return( rc ); + { + __mpu_uint64_t p; + rc = frame_offset( i->operand[0].value.off64, (__mpu_uint64_t)n, &p ); + if( rc ) return( rc ); + memmove( __STACK + p, a, n ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_LEAFP: + rc = getreg( &i->operand[0], &d, &n ); + if( rc ) return( rc ); + if( n != CPU_PTR64_SIZE ) return( CPU_EXEC_WIDTH_FAULT ); + if( i->operand[1].type != CPU_OPERAND_OFF64 ) return( CPU_EXEC_OPERAND_FAULT ); + { + __mpu_uint64_t pos, ptr; + rc = frame_offset( i->operand[1].value.off64, 0, &pos ); + if( rc ) return( rc ); + if( pos > CPU_PTR_OFFSET_MASK ) return( CPU_EXEC_POINTER_FAULT ); + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, pos ) ) return( CPU_EXEC_POINTER_FAULT ); + __cpu_data_put_u64( d, ptr ); + } + return( CPU_EXEC_SUCCESS ); + + case CPU_OP_SETSP: + rc = pointer_register( &i->operand[0], &a ); + if( rc ) return( rc ); + { + __mpu_uint64_t ptr, off; + unsigned int seg; + ptr = __cpu_data_get_u64( a ); + seg = __cpu_ptr_segment( ptr ); + off = __cpu_ptr_offset( ptr ); + if( seg != CPU_PTR_SEG_STACK ) return( CPU_EXEC_POINTER_FAULT ); + if( !__STACK || !__SSIZE || off > __SSIZE ) return( CPU_EXEC_STACK_FAULT ); + __SP = off; + } + return( CPU_EXEC_SUCCESS ); + + default: return( CPU_EXEC_OPCODE_FAULT ); + } +} + +static int +decode_need( __mpu_uint64_t program_size, __mpu_uint64_t pos, + __mpu_uint64_t n ) +{ + return( pos <= program_size && n <= program_size - pos ); +} + +static int +decode_reg( const __mpu_UNIT_t *program, __mpu_uint64_t program_size, + cpu_operand *o, __mpu_uint64_t *p ) +{ + if( !decode_need( program_size, *p, 3 ) || __cpu_decode_reg24( &o->value.reg, program + *p ) ) return( -1 ); + o->type = CPU_OPERAND_REGISTER; + *p += 3; + return( 0 ); +} + +static int +decode_u64( const __mpu_UNIT_t *program, __mpu_uint64_t program_size, + cpu_operand *o, __mpu_uint64_t *p, cpu_operand_type t ) +{ + __mpu_uint64_t v; + if( !decode_need( program_size, *p, 8 ) ) return( -1 ); + v = __cpu_code_get_u64( program + *p ); + *p += 8; + o->type = t; + if( t == CPU_OPERAND_IMM64 ) o->value.imm64 = v; + else if( t == CPU_OPERAND_ADDR64 ) o->value.addr64 = v; + else if( t == CPU_OPERAND_CODE64 ) o->value.code64 = v; + else if( t == CPU_OPERAND_OFF64 ) memcpy( &o->value.off64, &v, sizeof( v ) ); + else if( t == CPU_OPERAND_PTR64 ) o->value.ptr64 = v; + else return( -1 ); + return( 0 ); +} + +static int +decode_u8( const __mpu_UNIT_t *program, __mpu_uint64_t program_size, + cpu_operand *o, __mpu_uint64_t *p, cpu_operand_type t ) +{ + __mpu_uint8_t v; + if( !decode_need( program_size, *p, 1 ) ) return( -1 ); + v = program[ ( *p )++]; + o->type = t; + if( t == CPU_OPERAND_CONST8 ) o->value.const8 = v; + else if( t == CPU_OPERAND_PSWBIT8 ) o->value.pswbit8 = v; + else return( -1 ); + return( 0 ); +} + +int +__cpu_decode_at( const __mpu_UNIT_t *program, __mpu_uint64_t program_size, + __mpu_uint64_t pc, cpu_instruction *i ) +{ + __mpu_uint64_t p = pc, start = p; + __mpu_uint16_t op; + int k; + if( !i || !program || !decode_need( program_size, p, 2 ) ) return( CPU_EXEC_DECODE_FAULT ); + memset( i, 0, sizeof( *i ) ); + op = __cpu_code_get_u16( program + p ); + p += 2; + i->opcode = (cpu_opcode)op; +#define DR(n) do{for(k=0;k<(n);k++)if(decode_reg(program,program_size,&i->operand[k],&p))return CPU_EXEC_DECODE_FAULT;}while(0) + switch( op ) + { + case CPU_OP_NOP:case CPU_OP_HALT:case CPU_OP_END:case CPU_OP_SYSCALL:case CPU_OP_RET: + case CPU_OP_CLRCPUF:case CPU_OP_CLRMPUF: + case CPU_OP_PUSHFP:case CPU_OP_SETFP:case CPU_OP_RESTSP:case CPU_OP_POPFP:break; + case CPU_OP_SETF:case CPU_OP_CLRF: + if( decode_u8( program, program_size, &i->operand[0], &p, CPU_OPERAND_PSWBIT8 ) ) return( CPU_EXEC_DECODE_FAULT ); + if( i->operand[0].value.pswbit8 > CPU_PSWBIT_WRITE_MAX ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_CLR: case CPU_OP_EXIT: DR( 1 ); + break; + case CPU_OP_MOV:case CPU_OP_SWAP:case CPU_OP_BSWAP: + case CPU_OP_INOT:case CPU_OP_INEG:case CPU_OP_ICMP:case CPU_OP_ITEST: + case CPU_OP_ISHL:case CPU_OP_ISHR:case CPU_OP_ISAL:case CPU_OP_ISAR: + case CPU_OP_IROL:case CPU_OP_IROR:case CPU_OP_IRCL:case CPU_OP_IRCR: + case CPU_OP_IINC:case CPU_OP_IDEC:case CPU_OP_IXCHG:case CPU_OP_ICPY: + case CPU_OP_ICVT:case CPU_OP_IBITSLEN: + case CPU_OP_RSQRT:case CPU_OP_RSIN:case CPU_OP_RCOS:case CPU_OP_RTAN: + case CPU_OP_RASIN:case CPU_OP_RACOS:case CPU_OP_RATAN: + case CPU_OP_RLOG:case CPU_OP_RLOG10:case CPU_OP_RLOG2:case CPU_OP_REXP: + case CPU_OP_RSINH:case CPU_OP_RCOSH:case CPU_OP_RTANH: + case CPU_OP_RNEG:case CPU_OP_RABS:case CPU_OP_RCPY:case CPU_OP_RCVT: + case CPU_OP_RFLOOR:case CPU_OP_RCEIL:case CPU_OP_RROUND:case CPU_OP_RLOGB: + case CPU_OP_RLOG1P:case CPU_OP_REXPM1:case CPU_OP_RASINH:case CPU_OP_RACOSH:case CPU_OP_RATANH: + case CPU_OP_ITOR:case CPU_OP_UITOR: + case CPU_OP_RISNEG:case CPU_OP_RSIGN:case CPU_OP_RISIND:case CPU_OP_RISNAN: + case CPU_OP_RISNANMAX:case CPU_OP_RISNANMIN:case CPU_OP_RISINF:case CPU_OP_RISSIGNULL: + case CPU_OP_CEXP:case CPU_OP_CSIN:case CPU_OP_CCOS:case CPU_OP_CSINH: + case CPU_OP_CCOSH:case CPU_OP_CLOG:case CPU_OP_CSQRT: + case CPU_OP_CREAL:case CPU_OP_CIMAG:case CPU_OP_CRTOC:case CPU_OP_CCONJ: + case CPU_OP_CABS:case CPU_OP_CNORM:case CPU_OP_CARG:case CPU_OP_CCPY:case CPU_OP_CCVT: + DR( 2 ); + break; + case CPU_OP_IADD:case CPU_OP_IADC:case CPU_OP_ISUB:case CPU_OP_ISBB: + case CPU_OP_IAND:case CPU_OP_IOR:case CPU_OP_IXOR: + case CPU_OP_ISHLN:case CPU_OP_ISHRN:case CPU_OP_ISALN:case CPU_OP_ISARN: + case CPU_OP_IROLN:case CPU_OP_IRORN:case CPU_OP_IRCLN:case CPU_OP_IRCRN: + case CPU_OP_IMUL:case CPU_OP_ISMUL: + case CPU_OP_RADD:case CPU_OP_RSUB:case CPU_OP_RMUL:case CPU_OP_RDIV: + case CPU_OP_RPOW:case CPU_OP_RHYPOT:case CPU_OP_RATAN2:case CPU_OP_RCMP: + case CPU_OP_RFREXP:case CPU_OP_RLDEXP:case CPU_OP_RMODF:case CPU_OP_RREMAIN: + case CPU_OP_RTOI:case CPU_OP_RTOUI: + case CPU_OP_CADD:case CPU_OP_CSUB:case CPU_OP_CMUL:case CPU_OP_CDIV:case CPU_OP_CPOW: + case CPU_OP_CGEN:case CPU_OP_CPOLAR:case CPU_OP_CISEQ:case CPU_OP_CISNE: + DR( 3 ); + break; + case CPU_OP_IDIV: case CPU_OP_ISDIV: DR( 4 ); + break; + case CPU_OP_LDI: case CPU_OP_LDR: case CPU_OP_LDC: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_IMM64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LOAD: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_ADDR64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_STORE: if( decode_u64( program, program_size, &i->operand[0], &p, CPU_OPERAND_ADDR64 ) ) return( CPU_EXEC_DECODE_FAULT ); + if( decode_reg( program, program_size, &i->operand[1], &p ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LEA: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_PTR64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LEASP: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LOADP: case CPU_OP_STOREP: DR( 2 ); + break; + case CPU_OP_PADD: case CPU_OP_PSUB: case CPU_OP_PDIFF: case CPU_OP_BMOV: DR( 3 ); + break; + case CPU_OP_BMOVI: + DR( 2 ); + if( decode_u64( program, program_size, &i->operand[2], &p, CPU_OPERAND_IMM64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_PADDI:case CPU_OP_PSUBI: + DR( 2 ); + if( decode_u64( program, program_size, &i->operand[2], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_RCONST: DR( 1 ); + if( decode_u8( program, program_size, &i->operand[1], &p, CPU_OPERAND_CONST8 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_JMP: case CPU_OP_CALL: if( decode_u64( program, program_size, &i->operand[0], &p, CPU_OPERAND_CODE64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_JMPP: case CPU_OP_CALLP: DR( 1 ); + break; + case CPU_OP_JSET:case CPU_OP_JCLR: + if( decode_u8( program, program_size, &i->operand[0], &p, CPU_OPERAND_PSWBIT8 ) ) return( CPU_EXEC_DECODE_FAULT ); + if( i->operand[0].value.pswbit8 > CPU_PSWBIT_MAX ) return( CPU_EXEC_DECODE_FAULT ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_CODE64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_PUSH: case CPU_OP_POP: DR( 1 ); + break; + case CPU_OP_LDSP: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_STSP: if( decode_u64( program, program_size, &i->operand[0], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + if( decode_reg( program, program_size, &i->operand[1], &p ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_ADJSP: if( decode_u64( program, program_size, &i->operand[0], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LDFP: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_STFP: if( decode_u64( program, program_size, &i->operand[0], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + if( decode_reg( program, program_size, &i->operand[1], &p ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_LEAFP: DR( 1 ); + if( decode_u64( program, program_size, &i->operand[1], &p, CPU_OPERAND_OFF64 ) ) return( CPU_EXEC_DECODE_FAULT ); + break; + case CPU_OP_SETSP: DR( 1 ); + break; + default: return( CPU_EXEC_OPCODE_FAULT ); + } +#undef DR + i->length = p - start; + return( 0 ); +} + + +int +__cpu_decode( cpu_instruction *i ) +{ + return( __cpu_decode_at( __PROGRAM, __PSIZE, __PC, i ) ); + +} /* End of __cpu_decode() */ + +int +__cpu_step( void ) +{ + cpu_instruction i; + int rc = __cpu_decode( &i ); + if( rc ) + { + __STOP_REASON = CPU_STOP_FAULT; + return( rc ); + } + rc = __cpu_exec( &i ); + if( rc == CPU_EXEC_BRANCH ) + { + if( __cpu_gth() ) + { + __STOP_REASON = CPU_STOP_HALT; + return( CPU_EXEC_HALT ); + } + return( CPU_EXEC_SUCCESS ); + } + if( rc == CPU_EXEC_END ) + { + __PC += i.length; + __STOP_REASON = CPU_STOP_END; + return( rc ); + } + if( rc == CPU_EXEC_EXIT ) + { + __PC += i.length; + __STOP_REASON = CPU_STOP_EXIT; + return( rc ); + } + if( rc < 0 ) + { + __STOP_REASON = CPU_STOP_FAULT; + return( rc ); + } + if( rc ) return( rc ); + __PC+=i.length; + if( __cpu_gth() ) + { + __STOP_REASON = CPU_STOP_HALT; + return( CPU_EXEC_HALT ); + } + return( CPU_EXEC_SUCCESS ); +} + +int +__cpu_run( void ) +{ + int rc; + __cpu_clh(); + __cpu_clear_stop_reason(); + for( ; ; ) + { + rc = __cpu_step(); + if( rc == CPU_EXEC_HALT ) return( CPU_EXEC_SUCCESS ); + if( rc != CPU_EXEC_SUCCESS ) return( rc ); + } +} diff --git a/cpu/cpu-instruction.h b/cpu/cpu-instruction.h new file mode 100644 index 0000000..d787b43 --- /dev/null +++ b/cpu/cpu-instruction.h @@ -0,0 +1,32 @@ +/*************************************************************** + __CPU_INSTRUCTION.H + + This file contains CPU INSTRUCTION operation functions & + data declarations. + + PART OF : CPU - library . + + USAGE : Internal only . + + NOTE : Include "libcpu.h" before this FILE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_INSTRUCTION_H +#define __CPU_INSTRUCTION_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#define __CPU_MAX_OPERANDS 4 + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* __CPU_INSTRUCTION_H */ diff --git a/cpu/cpu-registers.c b/cpu/cpu-registers.c new file mode 100644 index 0000000..7cd7dee --- /dev/null +++ b/cpu/cpu-registers.c @@ -0,0 +1,74 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <libmcpu.h> +#include <cpu-context.h> +#include <cpu-registers.h> + +__mpu_size_t +__cpu_reg_size( unsigned int bits ) +{ + if( bits < 8 || bits > 65536 || ( bits & ( bits - 1 ) ) ) return( 0 ); + return( (__mpu_size_t)( bits >> 3 ) ); +} + +int +__cpu_reg_valid( unsigned int bits, __mpu_uint32_t a ) +{ + __mpu_size_t n = __cpu_reg_size( bits ); + if( !n ) return( 0 ); + if( ( a & ( n - 1 ) ) != 0 ) return( 0 ); + if( a > CPU_REGISTER_ADDRESS_MAX ) return( 0 ); + if( (__mpu_uint64_t)a + n > CPU_REGISTERS_MEM_SIZE_IN_UNITS ) return( 0 ); + return( 1 ); +} + +__mpu_UNIT_t * +__cpu_reg_ptr( unsigned int bits, __mpu_uint32_t a ) +{ + return( __cpu_reg_valid( bits, a ) ? &__REGS[a] : (__mpu_UNIT_t *)0 ); +} + +int +__cpu_encode_reg24( __mpu_UNIT_t out[3], const cpu_register *r ) +{ + unsigned int code = 0, b; + if( !out || !r || !__cpu_reg_valid( r->bits, r->address ) ) return( -1 ); + b = r->bits; + while( b > 8 ) + { + b >>= 1; + ++code; + } + if( code > 13 ) return( -1 ); + out[0] = (__mpu_UNIT_t)( ( code << 4 ) | ( ( r->address >> 16 ) & 0x0f ) ); + out[1] = (__mpu_UNIT_t)( ( r->address >> 8 ) & 0xff ); + out[2] = (__mpu_UNIT_t)( r->address & 0xff ); + return( 0 ); +} + +int +__cpu_decode_reg24( cpu_register *r, const __mpu_UNIT_t in[3] ) +{ + unsigned int code; + __mpu_uint32_t a; + if( !r || !in ) return( -1 ); + code = ( in[0] >> 4 ) & 0x0f; + if( code > 13 ) return( -1 ); + a = ( (__mpu_uint32_t)( in[0] & 0x0f ) << 16 ) | ( (__mpu_uint32_t)in[1] << 8 ) | in[2]; + r->bits = 8U << code; + r->address = a; + return( __cpu_reg_valid( r->bits, a ) ? 0 : -1 ); +} + +int +__cpu_return_reg( cpu_register *r, unsigned int bits ) +{ + __mpu_size_t n; + if( !r ) return( -1 ); + n = __cpu_reg_size( bits ); + if( !n || (__mpu_uint64_t)n > CPU_RETURN_AREA_SIZE ) return( -1 ); + r->bits = bits; + r->address = (__mpu_uint32_t)( CPU_RETURN_AREA_END - (__mpu_uint32_t)n ); + return( __cpu_reg_valid( r->bits, r->address ) ? 0 : -1 ); +} diff --git a/cpu/cpu-registers.h b/cpu/cpu-registers.h new file mode 100644 index 0000000..f9bf9cb --- /dev/null +++ b/cpu/cpu-registers.h @@ -0,0 +1,33 @@ +/*************************************************************** + __CPU_REGISTERS.H + + This file contains CPU REGISTER operation functions & data + declarations. + + PART OF : CPU - library . + + USAGE : Internal only . + + NOTE : Include "libcpu.h" before this FILE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_REGISTERS_H +#define __CPU_REGISTERS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#define __CPU_REGISTER_MIN_BITS 8 +#define __CPU_REGISTER_MAX_BITS 65536 + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* __CPU_REGISTERS_H */ diff --git a/cpu/cpu-syscalls.c b/cpu/cpu-syscalls.c new file mode 100644 index 0000000..f12e729 --- /dev/null +++ b/cpu/cpu-syscalls.c @@ -0,0 +1,2174 @@ +/*************************************************************** + __CPU_SYSCALLS.C + + This file contains source code of MCPU system calls. + + PART OF : CPU - library . + + USAGE : Internal only . + + NOTE : NONE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <cpu-context.h> +#include <cpu-syscalls.h> +#include <cpu-sysdeps.h> + + +#define __NR_brk 10 +#define __NR_gettimeofday 0x01c +#define __NR_clock_getres 0x021 +#define __NR_clock_gettime 0x022 +#define __NR_nanosleep 0x030 +#define __NR_clock_nanosleep 0x031 + +#define __NR_read 0x100 +#define __NR_write 0x101 +#define __NR_lseek 0x102 +#define __NR_open 0x103 +#define __NR_creat 0x104 +#define __NR_close 0x105 +#define __NR_link 0x106 +#define __NR_unlink 0x107 +#define __NR_chdir 0x108 +#define __NR_mkdir 0x109 +#define __NR_chmod 0x10b +#define __NR_chown 0x10c +#define __NR_access 0x10f +#define __NR_rename 0x111 +#define __NR_rmdir 0x112 +#define __NR_symlink 0x113 +#define __NR_readlink 0x114 +#define __NR_stat 0x115 +#define __NR_fstat 0x116 +#define __NR_lstat 0x117 +#define __NR_fcntl 0x119 +#define __NR_pipe2 0x11a +#define __NR_getdents 0x11d +#define __NR_select 0x11e +#define __NR_fsync 0x120 +#define __NR_truncate 0x121 +#define __NR_ftruncate 0x122 +#define __NR_fchmod 0x123 +#define __NR_fchown 0x124 +#define __NR_fchdir 0x125 +#define __NR_dup 0x126 +#define __NR_dup2 0x127 + +#define CPU_PATH_MAX 4096U + + +static __mpu_uint64_t +__cpu_syscall_error( int error ) +{ + __mpu_int64_t ret = -(__mpu_int64_t)error; + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_syscall_error() */ + + +static __mpu_uint32_t +__cpu_syscall_nargs( void ) +{ + return( __cpu_data_get_u32( __cpu_reg_ptr( 32, CPU_SYSCALL_NARGS_ADDR ) ) ); + +} /* End of __cpu_syscall_nargs() */ + + +static __mpu_uint64_t +__cpu_syscall_arg( unsigned int n ) +{ + static const __mpu_uint32_t address[CPU_SYSCALL_MAX_ARGS] = + { + CPU_SYSCALL_ARG0_ADDR, + CPU_SYSCALL_ARG1_ADDR, + CPU_SYSCALL_ARG2_ADDR, + CPU_SYSCALL_ARG3_ADDR, + CPU_SYSCALL_ARG4_ADDR, + CPU_SYSCALL_ARG5_ADDR + }; + + if( n >= CPU_SYSCALL_MAX_ARGS ) return( 0 ); + + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address[n] ) ) ); + +} /* End of __cpu_syscall_arg() */ + + +static int +__cpu_memory_resolve( __mpu_uint64_t ptr, __mpu_uint64_t size, int write, + __mpu_UNIT_t **memory ) +{ + __mpu_UNIT_t *base = (__mpu_UNIT_t *)0; + __mpu_uint64_t limit = 0; + __mpu_uint64_t offset; + unsigned int segment; + + if( !memory ) return( __MCPU_EFAULT ); + *memory = (__mpu_UNIT_t *)0; + + if( ptr == CPU_NULL_PTR ) return( __MCPU_EFAULT ); + + segment = __cpu_ptr_segment( ptr ); + offset = __cpu_ptr_offset( ptr ); + + switch( segment ) + { + case CPU_PTR_SEG_DATA: + base = __DATA; + limit = __DSIZE; + break; + case CPU_PTR_SEG_STACK: + base = __STACK; + limit = __SSIZE; + break; + case CPU_PTR_SEG_PROGRAM: + if( write ) return( __MCPU_EFAULT ); + base = __PROGRAM; + limit = __PSIZE; + break; + case CPU_PTR_SEG_CONTROL: + if( write ) return( __MCPU_EFAULT ); + base = __CONTROL; + limit = __CSIZE; + break; + case CPU_PTR_SEG_INPUT: + if( write ) return( __MCPU_EFAULT ); + base = __INPUT; + limit = __ISIZE; + break; + case CPU_PTR_SEG_OUTPUT: + base = __OUTPUT; + limit = __OSIZE; + break; + case CPU_PTR_SEG_TRACE: + base = __TRACE; + limit = __TSIZE; + break; + case CPU_PTR_SEG_HEAP: + base = __HEAP; + limit = __HBRK; + if( offset < CPU_HEAP_BEGIN ) return( __MCPU_EFAULT ); + break; + default: + return( __MCPU_EFAULT ); + } + + if( !base || offset > limit || size > limit - offset ) return( __MCPU_EFAULT ); + + *memory = base + offset; + return( 0 ); + +} /* End of __cpu_memory_resolve() */ + + +static int +__cpu_copyin( void *dst, __mpu_uint64_t src, __mpu_uint64_t size ) +{ + __mpu_UNIT_t *memory; + int error; + + if( !size ) return( 0 ); + if( !dst ) return( __MCPU_EFAULT ); + + error = __cpu_memory_resolve( src, size, 0, &memory ); + if( error ) return( error ); + + memcpy( dst, memory, (size_t)size ); + return( 0 ); + +} /* End of __cpu_copyin() */ + + +static int +__cpu_copyout( __mpu_uint64_t dst, const void *src, __mpu_uint64_t size ) +{ + __mpu_UNIT_t *memory; + int error; + + if( !size ) return( 0 ); + if( !src ) return( __MCPU_EFAULT ); + + error = __cpu_memory_resolve( dst, size, 1, &memory ); + if( error ) return( error ); + + memcpy( memory, src, (size_t)size ); + return( 0 ); + +} /* End of __cpu_copyout() */ + + +static int +__cpu_utf8_valid( const unsigned char *s, size_t n ) +{ + size_t i = 0; + + while( i < n ) + { + unsigned int c = s[i++]; + + if( c < 0x80U ) continue; + + if( c >= 0xc2U && c <= 0xdfU ) + { + if( i >= n || ( s[i] & 0xc0U ) != 0x80U ) return( 0 ); + ++i; + continue; + } + + if( c == 0xe0U ) + { + if( i + 1 >= n || s[i] < 0xa0U || s[i] > 0xbfU || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; + continue; + } + + if( c >= 0xe1U && c <= 0xecU ) + { + if( i + 1 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; + continue; + } + + if( c == 0xedU ) + { + if( i + 1 >= n || s[i] < 0x80U || s[i] > 0x9fU || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; + continue; + } + + if( c >= 0xeeU && c <= 0xefU ) + { + if( i + 1 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; + continue; + } + + if( c == 0xf0U ) + { + if( i + 2 >= n || s[i] < 0x90U || s[i] > 0xbfU || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; + continue; + } + + if( c >= 0xf1U && c <= 0xf3U ) + { + if( i + 2 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; + continue; + } + + if( c == 0xf4U ) + { + if( i + 2 >= n || s[i] < 0x80U || s[i] > 0x8fU || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; + continue; + } + + return( 0 ); + } + + return( 1 ); + +} /* End of __cpu_utf8_valid() */ + + +static int +__cpu_copyinstr( char **dst, __mpu_uint64_t src ) +{ + __mpu_UNIT_t *memory; + __mpu_uint64_t offset; + __mpu_uint64_t limit; + __mpu_uint64_t n; + unsigned int segment; + char *s; + + if( !dst ) return( __MCPU_EFAULT ); + *dst = (char *)0; + + if( src == CPU_NULL_PTR ) return( __MCPU_EFAULT ); + + segment = __cpu_ptr_segment( src ); + offset = __cpu_ptr_offset( src ); + + if( __cpu_memory_resolve( src, 1, 0, &memory ) != 0 ) return( __MCPU_EFAULT ); + + switch( segment ) + { + case CPU_PTR_SEG_DATA: limit = __DSIZE; break; + case CPU_PTR_SEG_STACK: limit = __SSIZE; break; + case CPU_PTR_SEG_PROGRAM: limit = __PSIZE; break; + case CPU_PTR_SEG_CONTROL: limit = __CSIZE; break; + case CPU_PTR_SEG_INPUT: limit = __ISIZE; break; + case CPU_PTR_SEG_OUTPUT: limit = __OSIZE; break; + case CPU_PTR_SEG_TRACE: limit = __TSIZE; break; + case CPU_PTR_SEG_HEAP: limit = __HBRK; break; + default: return( __MCPU_EFAULT ); + } + + n = 0; + while( offset + n < limit && n < CPU_PATH_MAX ) + { + if( memory[n] == 0 ) break; + ++n; + } + + if( offset + n >= limit || n >= CPU_PATH_MAX ) return( __MCPU_ENAMETOOLONG ); + if( !__cpu_utf8_valid( memory, (size_t)n ) ) return( __MCPU_EILSEQ ); + + s = (char *)malloc( (size_t)n + 1U ); + if( !s ) return( __MCPU_ENOMEM ); + memcpy( s, memory, (size_t)n ); + s[n] = '\0'; + *dst = s; + + return( 0 ); + +} /* End of __cpu_copyinstr() */ + + +void +__cpu_fd_context_init( void ) +{ + unsigned int i; + + if( !pcctx ) return; + + for( i = 0; i < CPU_GUEST_FD_MAX; ++i ) + { + pcctx->_fd[i].host_fd = -1; + pcctx->_fd[i].owned = 0; + pcctx->_fd[i].flags = 0; + } + + pcctx->_fd[0].host_fd = 0; + pcctx->_fd[1].host_fd = 1; + pcctx->_fd[2].host_fd = 2; + pcctx->_fd[0].flags = 0; + pcctx->_fd[1].flags = 0; + pcctx->_fd[2].flags = 0; + pcctx->_fd_initialized = 1; + +} /* End of __cpu_fd_context_init() */ + + +void +__cpu_fd_context_free( void ) +{ + unsigned int i; + + if( !pcctx || !pcctx->_fd_initialized ) return; + + for( i = 0; i < CPU_GUEST_FD_MAX; ++i ) + { + if( pcctx->_fd[i].host_fd >= 0 && pcctx->_fd[i].owned ) + (void)__cpu_sys_close( pcctx->_fd[i].host_fd ); + + pcctx->_fd[i].host_fd = -1; + pcctx->_fd[i].owned = 0; + pcctx->_fd[i].flags = 0; + } + + pcctx->_fd_initialized = 0; + +} /* End of __cpu_fd_context_free() */ + + +void +__cpu_cwd_context_init( void ) +{ + __mpu_int64_t ret; + + if( !pcctx ) return; + + pcctx->_cwd_fd = -1; + pcctx->_cwd_initialized = 0; + + ret = __cpu_sys_cwd_init(); + if( ret < 0 ) return; + + pcctx->_cwd_fd = (int)ret; + pcctx->_cwd_initialized = 1; + +} /* End of __cpu_cwd_context_init() */ + + +void +__cpu_cwd_context_free( void ) +{ + if( !pcctx || !pcctx->_cwd_initialized ) return; + + if( pcctx->_cwd_fd >= 0 ) + (void)__cpu_sys_close( pcctx->_cwd_fd ); + + pcctx->_cwd_fd = -1; + pcctx->_cwd_initialized = 0; + +} /* End of __cpu_cwd_context_free() */ + + +static int +__cpu_fd_get( __mpu_uint64_t fd, int *host_fd ) +{ + if( !pcctx || !pcctx->_fd_initialized || fd >= CPU_GUEST_FD_MAX || + pcctx->_fd[fd].host_fd < 0 ) return( __MCPU_EBADF ); + + if( host_fd ) *host_fd = pcctx->_fd[fd].host_fd; + return( 0 ); + +} /* End of __cpu_fd_get() */ + + +static int +__cpu_fd_find_from( __mpu_uint64_t minimum ) +{ + unsigned int fd; + + if( !pcctx || !pcctx->_fd_initialized || minimum >= CPU_GUEST_FD_MAX ) + return( -1 ); + + for( fd = (unsigned int)minimum; fd < CPU_GUEST_FD_MAX; ++fd ) + if( pcctx->_fd[fd].host_fd < 0 ) return( (int)fd ); + + return( -1 ); + +} /* End of __cpu_fd_find_from() */ + + +static int +__cpu_fd_install_from( int host_fd, __mpu_uint64_t minimum, unsigned int flags ) +{ + int fd; + + if( host_fd < 0 ) return( -1 ); + + fd = __cpu_fd_find_from( minimum ); + if( fd < 0 ) return( -1 ); + + pcctx->_fd[fd].host_fd = host_fd; + pcctx->_fd[fd].owned = 1; + pcctx->_fd[fd].flags = flags; + + return( fd ); + +} /* End of __cpu_fd_install_from() */ + + +static int +__cpu_fd_install( int host_fd ) +{ + return( __cpu_fd_install_from( host_fd, 3, 0 ) ); + +} /* End of __cpu_fd_install() */ + + + + +static __mpu_uint64_t +__cpu_stat_i64_bits( __mpu_int64_t value ) +{ + __mpu_uint64_t bits; + + memcpy( &bits, &value, sizeof( bits ) ); + return( bits ); + +} /* End of __cpu_stat_i64_bits() */ + + +static int +__cpu_stat_copyout( __mpu_uint64_t ptr, const struct __cpu_stat *st ) +{ + __mpu_UNIT_t data[__CPU_STAT_SIZE]; + + if( !st ) return( __MCPU_EFAULT ); + + memset( data, 0, sizeof( data ) ); + + __cpu_data_put_u64( data + __CPU_STAT_ST_DEV, st->st_dev ); + __cpu_data_put_u64( data + __CPU_STAT_ST_INO, st->st_ino ); + __cpu_data_put_u64( data + __CPU_STAT_ST_MODE, st->st_mode ); + __cpu_data_put_u64( data + __CPU_STAT_ST_NLINK, st->st_nlink ); + __cpu_data_put_u64( data + __CPU_STAT_ST_UID, st->st_uid ); + __cpu_data_put_u64( data + __CPU_STAT_ST_GID, st->st_gid ); + __cpu_data_put_u64( data + __CPU_STAT_ST_RDEV, st->st_rdev ); + __cpu_data_put_u64( data + __CPU_STAT_ST_SIZE, __cpu_stat_i64_bits( st->st_size ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_BLKSIZE, __cpu_stat_i64_bits( st->st_blksize ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_BLOCKS, __cpu_stat_i64_bits( st->st_blocks ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_ATIME_SEC, __cpu_stat_i64_bits( st->st_atime_sec ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_ATIME_NSEC, st->st_atime_nsec ); + __cpu_data_put_u64( data + __CPU_STAT_ST_MTIME_SEC, __cpu_stat_i64_bits( st->st_mtime_sec ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_MTIME_NSEC, st->st_mtime_nsec ); + __cpu_data_put_u64( data + __CPU_STAT_ST_CTIME_SEC, __cpu_stat_i64_bits( st->st_ctime_sec ) ); + __cpu_data_put_u64( data + __CPU_STAT_ST_CTIME_NSEC, st->st_ctime_nsec ); + + return( __cpu_copyout( ptr, data, sizeof( data ) ) ); + +} /* End of __cpu_stat_copyout() */ + + +static __mpu_uint64_t +__cpu_dirent_i64_bits( __mpu_int64_t value ) +{ + __mpu_uint64_t bits; + + memcpy( &bits, &value, sizeof( bits ) ); + return( bits ); + +} /* End of __cpu_dirent_i64_bits() */ + + +static int +__cpu_dirent_copyout( __mpu_uint64_t ptr, const struct __cpu_dirent *de, + __mpu_uint64_t count ) +{ + __mpu_UNIT_t *data; + __mpu_uint64_t nrecords; + __mpu_uint64_t i; + int error; + + if( !de && count ) return( __MCPU_EFAULT ); + if( count % __CPU_DIRENT_SIZE != 0 ) return( __MCPU_EINVAL ); + if( count > (__mpu_uint64_t)(size_t)-1 ) return( __MCPU_EINVAL ); + + if( !count ) return( 0 ); + data = (__mpu_UNIT_t *)calloc( 1, (size_t)count ); + if( !data ) return( __MCPU_ENOMEM ); + + nrecords = count / __CPU_DIRENT_SIZE; + for( i = 0; i < nrecords; ++i ) + { + __mpu_UNIT_t *record = data + i * __CPU_DIRENT_SIZE; + + __cpu_data_put_u64( record + __CPU_DIRENT_D_INO, de[i].d_ino ); + __cpu_data_put_u64( record + __CPU_DIRENT_D_OFF, + __cpu_dirent_i64_bits( de[i].d_off ) ); + __cpu_data_put_u64( record + __CPU_DIRENT_D_RECLEN, de[i].d_reclen ); + __cpu_data_put_u64( record + __CPU_DIRENT_D_TYPE, de[i].d_type ); + memcpy( record + __CPU_DIRENT_D_NAME, de[i].d_name, + __CPU_DIRENT_NAME_SIZE ); + } + + error = __cpu_copyout( ptr, data, count ); + free( data ); + return( error ); + +} /* End of __cpu_dirent_copyout() */ + + +static __mpu_int64_t +__cpu_select_i64_from_bits( __mpu_uint64_t bits ) +{ + __mpu_int64_t value; + + memcpy( &value, &bits, sizeof( value ) ); + return( value ); + +} /* End of __cpu_select_i64_from_bits() */ + + +static int +__cpu_fdset_load( __mpu_uint64_t ptr, __mpu_uint64_t words[__CPU_FD_SET_WORDS] ) +{ + __mpu_UNIT_t data[__CPU_FD_SET_SIZE]; + __mpu_UNIT_t *memory; + unsigned int i; + int error; + + error = __cpu_memory_resolve( ptr, __CPU_FD_SET_SIZE, 1, &memory ); + if( error ) return( error ); + (void)memory; + + error = __cpu_copyin( data, ptr, sizeof( data ) ); + if( error ) return( error ); + + for( i = 0; i < __CPU_FD_SET_WORDS; ++i ) + words[i] = __cpu_data_get_u64( data + i * 8U ); + + return( 0 ); + +} /* End of __cpu_fdset_load() */ + + +static int +__cpu_fdset_store( __mpu_uint64_t ptr, + const __mpu_uint64_t words[__CPU_FD_SET_WORDS] ) +{ + __mpu_UNIT_t data[__CPU_FD_SET_SIZE]; + unsigned int i; + + memset( data, 0, sizeof( data ) ); + for( i = 0; i < __CPU_FD_SET_WORDS; ++i ) + __cpu_data_put_u64( data + i * 8U, words[i] ); + + return( __cpu_copyout( ptr, data, sizeof( data ) ) ); + +} /* End of __cpu_fdset_store() */ + + +static int +__cpu_fdset_isset( const __mpu_uint64_t words[__CPU_FD_SET_WORDS], + unsigned int fd ) +{ + unsigned int word = fd / 64U; + unsigned int bit = fd % 64U; + + return( ( words[word] & ( (__mpu_uint64_t)1U << bit ) ) != 0 ); + +} /* End of __cpu_fdset_isset() */ + + +static void +__cpu_fdset_set( __mpu_uint64_t words[__CPU_FD_SET_WORDS], unsigned int fd ) +{ + unsigned int word = fd / 64U; + unsigned int bit = fd % 64U; + + words[word] |= ( (__mpu_uint64_t)1U << bit ); + +} /* End of __cpu_fdset_set() */ + + +static int +__cpu_timeval_load( __mpu_uint64_t ptr, struct __cpu_timeval *timeout ) +{ + __mpu_UNIT_t data[__CPU_TIMEVAL_SIZE]; + int error; + + if( !timeout ) return( __MCPU_EFAULT ); + + error = __cpu_copyin( data, ptr, sizeof( data ) ); + if( error ) return( error ); + + timeout->tv_sec = __cpu_select_i64_from_bits( + __cpu_data_get_u64( data + __CPU_TIMEVAL_TV_SEC ) ); + timeout->tv_usec = __cpu_select_i64_from_bits( + __cpu_data_get_u64( data + __CPU_TIMEVAL_TV_USEC ) ); + + if( timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec > 999999 ) + return( __MCPU_EINVAL ); + + return( 0 ); + +} /* End of __cpu_timeval_load() */ + + +static int +__cpu_timespec_load( __mpu_uint64_t ptr, struct __cpu_timespec *ts ) +{ + __mpu_UNIT_t data[__CPU_TIMESPEC_SIZE]; + int error; + + if( !ts ) return( __MCPU_EFAULT ); + + error = __cpu_copyin( data, ptr, sizeof( data ) ); + if( error ) return( error ); + + ts->tv_sec = __cpu_select_i64_from_bits( + __cpu_data_get_u64( data + __CPU_TIMESPEC_TV_SEC ) ); + ts->tv_nsec = __cpu_select_i64_from_bits( + __cpu_data_get_u64( data + __CPU_TIMESPEC_TV_NSEC ) ); + + if( ts->tv_sec < 0 || ts->tv_nsec < 0 || ts->tv_nsec > 999999999 ) + return( __MCPU_EINVAL ); + + return( 0 ); + +} /* End of __cpu_timespec_load() */ + + +static __mpu_uint64_t +__cpu_time_i64_bits( __mpu_int64_t value ) +{ + __mpu_uint64_t bits; + + memcpy( &bits, &value, sizeof( bits ) ); + return( bits ); + +} /* End of __cpu_time_i64_bits() */ + + +static int +__cpu_timeval_copyout( __mpu_uint64_t ptr, const struct __cpu_timeval *tv ) +{ + __mpu_UNIT_t data[__CPU_TIMEVAL_SIZE]; + + if( !tv ) return( __MCPU_EFAULT ); + + memset( data, 0, sizeof( data ) ); + __cpu_data_put_u64( data + __CPU_TIMEVAL_TV_SEC, + __cpu_time_i64_bits( tv->tv_sec ) ); + __cpu_data_put_u64( data + __CPU_TIMEVAL_TV_USEC, + __cpu_time_i64_bits( tv->tv_usec ) ); + + return( __cpu_copyout( ptr, data, sizeof( data ) ) ); + +} /* End of __cpu_timeval_copyout() */ + + +static int +__cpu_timespec_copyout( __mpu_uint64_t ptr, const struct __cpu_timespec *ts ) +{ + __mpu_UNIT_t data[__CPU_TIMESPEC_SIZE]; + + if( !ts ) return( __MCPU_EFAULT ); + + memset( data, 0, sizeof( data ) ); + __cpu_data_put_u64( data + __CPU_TIMESPEC_TV_SEC, + __cpu_time_i64_bits( ts->tv_sec ) ); + __cpu_data_put_u64( data + __CPU_TIMESPEC_TV_NSEC, + __cpu_time_i64_bits( ts->tv_nsec ) ); + + return( __cpu_copyout( ptr, data, sizeof( data ) ) ); + +} /* End of __cpu_timespec_copyout() */ + + +static __mpu_uint64_t +__cpu_current_brk( void ) +{ + __mpu_uint64_t ptr = CPU_NULL_PTR; + + if( !__HEAP || __HLIMIT < CPU_HEAP_BEGIN || __HBRK < CPU_HEAP_BEGIN ) + return( CPU_NULL_PTR ); + + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, __HBRK ) != 0 ) + return( CPU_NULL_PTR ); + + return( ptr ); + +} /* End of __cpu_current_brk() */ + + +__mpu_uint64_t +__cpu_SYS_brk( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t arg; + __mpu_uint64_t brk; + __mpu_uint64_t ptr = CPU_NULL_PTR; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_current_brk() ); + + arg = __cpu_syscall_arg( 0 ); + + if( arg == CPU_NULL_PTR ) return( __cpu_current_brk() ); + + if( __cpu_ptr_segment( arg ) != CPU_PTR_SEG_HEAP ) + return( __cpu_current_brk() ); + + brk = __cpu_heap_brk( __cpu_ptr_offset( arg ) ); + + if( brk < CPU_HEAP_BEGIN || __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, brk ) != 0 ) + return( CPU_NULL_PTR ); + + return( ptr ); + +} /* End of __cpu_SYS_brk() */ + + +__mpu_uint64_t +__cpu_SYS_close( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( pcctx->_fd[fd].owned ) + { + ret = __cpu_sys_close( host_fd ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + } + + pcctx->_fd[fd].host_fd = -1; + pcctx->_fd[fd].owned = 0; + pcctx->_fd[fd].flags = 0; + + return( 0 ); + +} /* End of __cpu_SYS_close() */ + + +__mpu_uint64_t +__cpu_SYS_dup( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int new_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + new_fd = __cpu_fd_find_from( 0 ); + if( new_fd < 0 ) return( __cpu_syscall_error( __MCPU_EMFILE ) ); + + ret = __cpu_sys_dup( host_fd ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + pcctx->_fd[new_fd].host_fd = (int)ret; + pcctx->_fd[new_fd].owned = 1; + pcctx->_fd[new_fd].flags = 0; + + return( (__mpu_uint64_t)new_fd ); + +} /* End of __cpu_SYS_dup() */ + + +__mpu_uint64_t +__cpu_SYS_dup2( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t old_fd; + __mpu_uint64_t new_fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + old_fd = __cpu_syscall_arg( 0 ); + new_fd = __cpu_syscall_arg( 1 ); + + error = __cpu_fd_get( old_fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + if( new_fd >= CPU_GUEST_FD_MAX ) return( __cpu_syscall_error( __MCPU_EBADF ) ); + + if( old_fd == new_fd ) return( new_fd ); + + ret = __cpu_sys_dup( host_fd ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + if( pcctx->_fd[new_fd].host_fd >= 0 && pcctx->_fd[new_fd].owned ) + (void)__cpu_sys_close( pcctx->_fd[new_fd].host_fd ); + + pcctx->_fd[new_fd].host_fd = (int)ret; + pcctx->_fd[new_fd].owned = 1; + pcctx->_fd[new_fd].flags = 0; + + return( new_fd ); + +} /* End of __cpu_SYS_dup2() */ + + +__mpu_uint64_t +__cpu_SYS_write( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t ptr; + __mpu_uint64_t count; + __mpu_int64_t ret; + unsigned char *buf = (unsigned char *)0; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + ptr = __cpu_syscall_arg( 1 ); + count = __cpu_syscall_arg( 2 ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( count ) + { + if( count > (__mpu_uint64_t)(size_t)-1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + buf = (unsigned char *)malloc( (size_t)count ); + if( !buf ) return( __cpu_syscall_error( __MCPU_ENOMEM ) ); + error = __cpu_copyin( buf, ptr, count ); + if( error ) + { + free( buf ); + return( __cpu_syscall_error( error ) ); + } + } + + ret = __cpu_sys_write( host_fd, buf, count ); + free( buf ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_write() */ + + +__mpu_uint64_t +__cpu_SYS_read( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t ptr; + __mpu_uint64_t count; + __mpu_int64_t ret; + unsigned char *buf = (unsigned char *)0; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + ptr = __cpu_syscall_arg( 1 ); + count = __cpu_syscall_arg( 2 ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( count ) + { + __mpu_UNIT_t *memory; + + if( count > (__mpu_uint64_t)(size_t)-1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + error = __cpu_memory_resolve( ptr, count, 1, &memory ); + if( error ) return( __cpu_syscall_error( error ) ); + + buf = (unsigned char *)malloc( (size_t)count ); + if( !buf ) return( __cpu_syscall_error( __MCPU_ENOMEM ) ); + } + + ret = __cpu_sys_read( host_fd, buf, count ); + if( ret > 0 ) + { + error = __cpu_copyout( ptr, buf, (__mpu_uint64_t)ret ); + if( error ) ret = -(__mpu_int64_t)error; + } + + free( buf ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_read() */ + + +__mpu_uint64_t +__cpu_SYS_lseek( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t bits; + __mpu_int64_t offset; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + bits = __cpu_syscall_arg( 1 ); + memcpy( &offset, &bits, sizeof( offset ) ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_lseek( host_fd, offset, __cpu_syscall_arg( 2 ) ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_lseek() */ + + +__mpu_uint64_t +__cpu_SYS_open( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_open( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ), + __cpu_syscall_arg( 2 ) ); + free( path ); + + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + fd = __cpu_fd_install( (int)ret ); + if( fd < 0 ) + { + (void)__cpu_sys_close( (int)ret ); + return( __cpu_syscall_error( __MCPU_EMFILE ) ); + } + + return( (__mpu_uint64_t)fd ); + +} /* End of __cpu_SYS_open() */ + + +__mpu_uint64_t +__cpu_SYS_creat( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_creat( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ) ); + free( path ); + + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + fd = __cpu_fd_install( (int)ret ); + if( fd < 0 ) + { + (void)__cpu_sys_close( (int)ret ); + return( __cpu_syscall_error( __MCPU_EMFILE ) ); + } + + return( (__mpu_uint64_t)fd ); + +} /* End of __cpu_SYS_creat() */ + + +__mpu_uint64_t +__cpu_SYS_link( __mpu_uint32_t sysno ) +{ + char *oldpath = (char *)0; + char *newpath = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &oldpath, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + error = __cpu_copyinstr( &newpath, __cpu_syscall_arg( 1 ) ); + if( error ) + { + free( oldpath ); + return( __cpu_syscall_error( error ) ); + } + + ret = __cpu_sys_link( pcctx->_cwd_fd, oldpath, newpath ); + free( newpath ); + free( oldpath ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_link() */ + + +__mpu_uint64_t +__cpu_SYS_unlink( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_unlink( pcctx->_cwd_fd, path ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_unlink() */ + + +__mpu_uint64_t +__cpu_SYS_chdir( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int old_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_chdir( pcctx->_cwd_fd, path ); + free( path ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + old_fd = pcctx->_cwd_fd; + pcctx->_cwd_fd = (int)ret; + pcctx->_cwd_initialized = 1; + if( old_fd >= 0 ) (void)__cpu_sys_close( old_fd ); + + return( 0 ); + +} /* End of __cpu_SYS_chdir() */ + + +__mpu_uint64_t +__cpu_SYS_fchdir( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int old_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_fchdir( host_fd ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + old_fd = pcctx->_cwd_fd; + pcctx->_cwd_fd = (int)ret; + pcctx->_cwd_initialized = 1; + if( old_fd >= 0 ) (void)__cpu_sys_close( old_fd ); + + return( 0 ); + +} /* End of __cpu_SYS_fchdir() */ + + +__mpu_uint64_t +__cpu_SYS_rename( __mpu_uint32_t sysno ) +{ + char *oldpath = (char *)0; + char *newpath = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &oldpath, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + error = __cpu_copyinstr( &newpath, __cpu_syscall_arg( 1 ) ); + if( error ) + { + free( oldpath ); + return( __cpu_syscall_error( error ) ); + } + + ret = __cpu_sys_rename( pcctx->_cwd_fd, oldpath, newpath ); + free( newpath ); + free( oldpath ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_rename() */ + + +__mpu_uint64_t +__cpu_SYS_mkdir( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_mkdir( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ) ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_mkdir() */ + + +__mpu_uint64_t +__cpu_SYS_chmod( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_chmod( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ) ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_chmod() */ + + +__mpu_uint64_t +__cpu_SYS_chown( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_chown( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ), + __cpu_syscall_arg( 2 ) ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_chown() */ + + +__mpu_uint64_t +__cpu_SYS_access( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_access( pcctx->_cwd_fd, path, __cpu_syscall_arg( 1 ) ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_access() */ + + +__mpu_uint64_t +__cpu_SYS_rmdir( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_rmdir( pcctx->_cwd_fd, path ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_rmdir() */ + + +__mpu_uint64_t +__cpu_SYS_symlink( __mpu_uint32_t sysno ) +{ + char *target = (char *)0; + char *linkpath = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &target, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + error = __cpu_copyinstr( &linkpath, __cpu_syscall_arg( 1 ) ); + if( error ) + { + free( target ); + return( __cpu_syscall_error( error ) ); + } + + ret = __cpu_sys_symlink( pcctx->_cwd_fd, target, linkpath ); + free( linkpath ); + free( target ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_symlink() */ + + +__mpu_uint64_t +__cpu_SYS_readlink( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_uint64_t ptr; + __mpu_uint64_t count; + __mpu_int64_t ret; + unsigned char *buf = (unsigned char *)0; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ptr = __cpu_syscall_arg( 1 ); + count = __cpu_syscall_arg( 2 ); + + if( count ) + { + __mpu_UNIT_t *memory; + + if( count > (__mpu_uint64_t)(size_t)-1 ) + { + free( path ); + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + } + + error = __cpu_memory_resolve( ptr, count, 1, &memory ); + if( error ) + { + free( path ); + return( __cpu_syscall_error( error ) ); + } + + buf = (unsigned char *)malloc( (size_t)count ); + if( !buf ) + { + free( path ); + return( __cpu_syscall_error( __MCPU_ENOMEM ) ); + } + } + + ret = __cpu_sys_readlink( pcctx->_cwd_fd, path, buf, count ); + free( path ); + + if( ret > 0 ) + { + error = __cpu_copyout( ptr, buf, (__mpu_uint64_t)ret ); + if( error ) ret = -(__mpu_int64_t)error; + } + + free( buf ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_readlink() */ + + +__mpu_uint64_t +__cpu_SYS_stat( __mpu_uint32_t sysno ) +{ + struct __cpu_stat st; + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_stat( pcctx->_cwd_fd, path, &st ); + free( path ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + error = __cpu_stat_copyout( __cpu_syscall_arg( 1 ), &st ); + if( error ) return( __cpu_syscall_error( error ) ); + + return( 0 ); + +} /* End of __cpu_SYS_stat() */ + + +__mpu_uint64_t +__cpu_SYS_fstat( __mpu_uint32_t sysno ) +{ + struct __cpu_stat st; + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_fstat( host_fd, &st ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + error = __cpu_stat_copyout( __cpu_syscall_arg( 1 ), &st ); + if( error ) return( __cpu_syscall_error( error ) ); + + return( 0 ); + +} /* End of __cpu_SYS_fstat() */ + + +__mpu_uint64_t +__cpu_SYS_lstat( __mpu_uint32_t sysno ) +{ + struct __cpu_stat st; + char *path = (char *)0; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_lstat( pcctx->_cwd_fd, path, &st ); + free( path ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + error = __cpu_stat_copyout( __cpu_syscall_arg( 1 ), &st ); + if( error ) return( __cpu_syscall_error( error ) ); + + return( 0 ); + +} /* End of __cpu_SYS_lstat() */ + + +__mpu_uint64_t +__cpu_SYS_getdents( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t ptr; + __mpu_uint64_t count; + __mpu_uint64_t usable; + __mpu_int64_t ret; + struct __cpu_dirent *buf = (struct __cpu_dirent *)0; + __mpu_UNIT_t *memory; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + ptr = __cpu_syscall_arg( 1 ); + count = __cpu_syscall_arg( 2 ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( count < __CPU_DIRENT_SIZE ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + if( count > (__mpu_uint64_t)(size_t)-1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_memory_resolve( ptr, count, 1, &memory ); + if( error ) return( __cpu_syscall_error( error ) ); + + usable = ( count / __CPU_DIRENT_SIZE ) * __CPU_DIRENT_SIZE; + buf = (struct __cpu_dirent *)calloc( (size_t)( usable / __CPU_DIRENT_SIZE ), + sizeof( *buf ) ); + if( !buf ) return( __cpu_syscall_error( __MCPU_ENOMEM ) ); + + ret = __cpu_sys_getdents( host_fd, buf, usable ); + if( ret > 0 ) + { + error = __cpu_dirent_copyout( ptr, buf, (__mpu_uint64_t)ret ); + if( error ) ret = -(__mpu_int64_t)error; + } + + free( buf ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_getdents() */ + + +__mpu_uint64_t +__cpu_SYS_fcntl( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t cmd; + __mpu_uint64_t arg; + __mpu_int64_t ret; + int host_fd; + int new_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + cmd = __cpu_syscall_arg( 1 ); + arg = __cpu_syscall_arg( 2 ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + switch( cmd ) + { + case __CPU_F_DUPFD: + if( arg >= CPU_GUEST_FD_MAX ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + new_fd = __cpu_fd_find_from( arg ); + if( new_fd < 0 ) return( __cpu_syscall_error( __MCPU_EMFILE ) ); + + ret = __cpu_sys_fcntl( host_fd, cmd, 0 ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + pcctx->_fd[new_fd].host_fd = (int)ret; + pcctx->_fd[new_fd].owned = 1; + pcctx->_fd[new_fd].flags = 0; + return( (__mpu_uint64_t)new_fd ); + + case __CPU_F_GETFD: + if( arg != 0 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + return( (__mpu_uint64_t)( pcctx->_fd[fd].flags & __CPU_FD_SUPPORTED ) ); + + case __CPU_F_SETFD: + if( arg & ~(__mpu_uint64_t)__CPU_FD_SUPPORTED ) + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + pcctx->_fd[fd].flags = (unsigned int)arg; + return( 0 ); + + case __CPU_F_GETFL: + if( arg != 0 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + ret = __cpu_sys_fcntl( host_fd, cmd, 0 ); + return( (__mpu_uint64_t)ret ); + + case __CPU_F_SETFL: + ret = __cpu_sys_fcntl( host_fd, cmd, arg ); + return( (__mpu_uint64_t)ret ); + + default: + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + } + +} /* End of __cpu_SYS_fcntl() */ + + +__mpu_uint64_t +__cpu_SYS_pipe2( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t ptr; + __mpu_uint64_t flags; + __mpu_UNIT_t data[__CPU_PIPEFD_SIZE]; + __mpu_UNIT_t *memory; + __mpu_int64_t ret; + unsigned int fd_flags; + int host_pipe[2] = { -1, -1 }; + int read_fd; + int write_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + ptr = __cpu_syscall_arg( 0 ); + flags = __cpu_syscall_arg( 1 ); + + if( flags & ~(__mpu_uint64_t)__CPU_PIPE2_SUPPORTED ) + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_memory_resolve( ptr, __CPU_PIPEFD_SIZE, 1, &memory ); + if( error ) return( __cpu_syscall_error( error ) ); + (void)memory; + + read_fd = __cpu_fd_find_from( 3 ); + if( read_fd < 0 ) return( __cpu_syscall_error( __MCPU_EMFILE ) ); + + write_fd = __cpu_fd_find_from( (__mpu_uint64_t)read_fd + 1U ); + if( write_fd < 0 ) return( __cpu_syscall_error( __MCPU_EMFILE ) ); + + ret = __cpu_sys_pipe2( host_pipe, flags & __CPU_O_NONBLOCK ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + memset( data, 0, sizeof( data ) ); + __cpu_data_put_u64( data + __CPU_PIPEFD_READ, (__mpu_uint64_t)read_fd ); + __cpu_data_put_u64( data + __CPU_PIPEFD_WRITE, (__mpu_uint64_t)write_fd ); + + error = __cpu_copyout( ptr, data, sizeof( data ) ); + if( error ) + { + (void)__cpu_sys_close( host_pipe[0] ); + (void)__cpu_sys_close( host_pipe[1] ); + return( __cpu_syscall_error( error ) ); + } + + fd_flags = ( flags & __CPU_O_CLOEXEC ) ? __CPU_FD_CLOEXEC : 0U; + + pcctx->_fd[read_fd].host_fd = host_pipe[0]; + pcctx->_fd[read_fd].owned = 1; + pcctx->_fd[read_fd].flags = fd_flags; + + pcctx->_fd[write_fd].host_fd = host_pipe[1]; + pcctx->_fd[write_fd].owned = 1; + pcctx->_fd[write_fd].flags = fd_flags; + + return( 0 ); + +} /* End of __cpu_SYS_pipe2() */ + + + +__mpu_uint64_t +__cpu_SYS_select( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t nfds; + __mpu_uint64_t read_ptr; + __mpu_uint64_t write_ptr; + __mpu_uint64_t except_ptr; + __mpu_uint64_t timeout_ptr; + __mpu_uint64_t read_in[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + __mpu_uint64_t write_in[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + __mpu_uint64_t except_in[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + __mpu_uint64_t read_out[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + __mpu_uint64_t write_out[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + __mpu_uint64_t except_out[__CPU_FD_SET_WORDS] = { 0, 0, 0, 0 }; + struct __cpu_select_entry entries[CPU_GUEST_FD_MAX]; + struct __cpu_timeval timeout; + const struct __cpu_timeval *ptimeout = (const struct __cpu_timeval *)0; + __mpu_int64_t ret; + __mpu_uint64_t nentries = 0; + __mpu_uint64_t ready = 0; + unsigned int fd; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 5 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + nfds = __cpu_syscall_arg( 0 ); + read_ptr = __cpu_syscall_arg( 1 ); + write_ptr = __cpu_syscall_arg( 2 ); + except_ptr = __cpu_syscall_arg( 3 ); + timeout_ptr = __cpu_syscall_arg( 4 ); + + if( nfds > CPU_GUEST_FD_MAX ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + if( read_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_load( read_ptr, read_in ); + if( error ) return( __cpu_syscall_error( error ) ); + } + if( write_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_load( write_ptr, write_in ); + if( error ) return( __cpu_syscall_error( error ) ); + } + if( except_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_load( except_ptr, except_in ); + if( error ) return( __cpu_syscall_error( error ) ); + } + if( timeout_ptr != CPU_NULL_PTR ) + { + error = __cpu_timeval_load( timeout_ptr, &timeout ); + if( error ) return( __cpu_syscall_error( error ) ); + ptimeout = &timeout; + } + + memset( entries, 0, sizeof( entries ) ); + + for( fd = 0; fd < (unsigned int)nfds; ++fd ) + { + unsigned int events = 0; + + if( read_ptr != CPU_NULL_PTR && __cpu_fdset_isset( read_in, fd ) ) + events |= __CPU_SELECT_READ; + if( write_ptr != CPU_NULL_PTR && __cpu_fdset_isset( write_in, fd ) ) + events |= __CPU_SELECT_WRITE; + if( except_ptr != CPU_NULL_PTR && __cpu_fdset_isset( except_in, fd ) ) + events |= __CPU_SELECT_EXCEPT; + + if( !events ) continue; + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + entries[nentries].guest_fd = fd; + entries[nentries].host_fd = host_fd; + entries[nentries].events = events; + entries[nentries].revents = 0; + ++nentries; + } + + ret = __cpu_sys_select( entries, nentries, ptimeout ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + for( nfds = 0; nfds < nentries; ++nfds ) + { + fd = entries[nfds].guest_fd; + + if( ( entries[nfds].revents & __CPU_SELECT_READ ) && read_ptr != CPU_NULL_PTR ) + { + __cpu_fdset_set( read_out, fd ); + ++ready; + } + if( ( entries[nfds].revents & __CPU_SELECT_WRITE ) && write_ptr != CPU_NULL_PTR ) + { + __cpu_fdset_set( write_out, fd ); + ++ready; + } + if( ( entries[nfds].revents & __CPU_SELECT_EXCEPT ) && except_ptr != CPU_NULL_PTR ) + { + __cpu_fdset_set( except_out, fd ); + ++ready; + } + } + + if( read_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_store( read_ptr, read_out ); + if( error ) return( __cpu_syscall_error( error ) ); + } + if( write_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_store( write_ptr, write_out ); + if( error ) return( __cpu_syscall_error( error ) ); + } + if( except_ptr != CPU_NULL_PTR ) + { + error = __cpu_fdset_store( except_ptr, except_out ); + if( error ) return( __cpu_syscall_error( error ) ); + } + + return( ready ); + +} /* End of __cpu_SYS_select() */ + +__mpu_uint64_t +__cpu_SYS_gettimeofday( __mpu_uint32_t sysno ) +{ + struct __cpu_timespec ts; + struct __cpu_timeval tv; + __mpu_uint64_t tv_ptr; + __mpu_uint64_t tz_ptr; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + tv_ptr = __cpu_syscall_arg( 0 ); + tz_ptr = __cpu_syscall_arg( 1 ); + + if( tv_ptr == CPU_NULL_PTR ) return( __cpu_syscall_error( __MCPU_EFAULT ) ); + if( tz_ptr != CPU_NULL_PTR ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + ret = __cpu_sys_clock_gettime( __CPU_CLOCK_REALTIME, &ts ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec / 1000; + + error = __cpu_timeval_copyout( tv_ptr, &tv ); + if( error ) return( __cpu_syscall_error( error ) ); + + return( 0 ); + +} /* End of __cpu_SYS_gettimeofday() */ + + +__mpu_uint64_t +__cpu_SYS_clock_getres( __mpu_uint32_t sysno ) +{ + struct __cpu_timespec res; + __mpu_uint64_t clockid; + __mpu_uint64_t res_ptr; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + clockid = __cpu_syscall_arg( 0 ); + res_ptr = __cpu_syscall_arg( 1 ); + + ret = __cpu_sys_clock_getres( clockid, + res_ptr == CPU_NULL_PTR ? (struct __cpu_timespec *)0 : &res ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + if( res_ptr != CPU_NULL_PTR ) + { + error = __cpu_timespec_copyout( res_ptr, &res ); + if( error ) return( __cpu_syscall_error( error ) ); + } + + return( 0 ); + +} /* End of __cpu_SYS_clock_getres() */ + + +__mpu_uint64_t +__cpu_SYS_clock_gettime( __mpu_uint32_t sysno ) +{ + struct __cpu_timespec ts; + __mpu_uint64_t clockid; + __mpu_uint64_t tp_ptr; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + clockid = __cpu_syscall_arg( 0 ); + tp_ptr = __cpu_syscall_arg( 1 ); + + if( tp_ptr == CPU_NULL_PTR ) return( __cpu_syscall_error( __MCPU_EFAULT ) ); + + ret = __cpu_sys_clock_gettime( clockid, &ts ); + if( ret < 0 ) return( (__mpu_uint64_t)ret ); + + error = __cpu_timespec_copyout( tp_ptr, &ts ); + if( error ) return( __cpu_syscall_error( error ) ); + + return( 0 ); + +} /* End of __cpu_SYS_clock_gettime() */ + + +__mpu_uint64_t +__cpu_SYS_nanosleep( __mpu_uint32_t sysno ) +{ + struct __cpu_timespec req; + struct __cpu_timespec rem; + __mpu_uint64_t req_ptr; + __mpu_uint64_t rem_ptr; + __mpu_UNIT_t *memory; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + req_ptr = __cpu_syscall_arg( 0 ); + rem_ptr = __cpu_syscall_arg( 1 ); + + if( req_ptr == CPU_NULL_PTR ) return( __cpu_syscall_error( __MCPU_EFAULT ) ); + + error = __cpu_timespec_load( req_ptr, &req ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( rem_ptr != CPU_NULL_PTR ) + { + error = __cpu_memory_resolve( rem_ptr, __CPU_TIMESPEC_SIZE, 1, &memory ); + if( error ) return( __cpu_syscall_error( error ) ); + (void)memory; + } + + ret = __cpu_sys_clock_nanosleep( __CPU_CLOCK_MONOTONIC, 0, &req, + rem_ptr == CPU_NULL_PTR ? (struct __cpu_timespec *)0 : &rem ); + + if( ret == -(__mpu_int64_t)__MCPU_EINTR && rem_ptr != CPU_NULL_PTR ) + { + error = __cpu_timespec_copyout( rem_ptr, &rem ); + if( error ) return( __cpu_syscall_error( error ) ); + } + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_nanosleep() */ + + +__mpu_uint64_t +__cpu_SYS_clock_nanosleep( __mpu_uint32_t sysno ) +{ + struct __cpu_timespec req; + struct __cpu_timespec rem; + __mpu_uint64_t clockid; + __mpu_uint64_t flags; + __mpu_uint64_t req_ptr; + __mpu_uint64_t rem_ptr; + __mpu_UNIT_t *memory; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 4 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + clockid = __cpu_syscall_arg( 0 ); + flags = __cpu_syscall_arg( 1 ); + req_ptr = __cpu_syscall_arg( 2 ); + rem_ptr = __cpu_syscall_arg( 3 ); + + if( flags & ~(__mpu_uint64_t)__CPU_TIMER_ABSTIME ) + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + if( req_ptr == CPU_NULL_PTR ) return( __cpu_syscall_error( __MCPU_EFAULT ) ); + + error = __cpu_timespec_load( req_ptr, &req ); + if( error ) return( __cpu_syscall_error( error ) ); + + if( !( flags & __CPU_TIMER_ABSTIME ) && rem_ptr != CPU_NULL_PTR ) + { + error = __cpu_memory_resolve( rem_ptr, __CPU_TIMESPEC_SIZE, 1, &memory ); + if( error ) return( __cpu_syscall_error( error ) ); + (void)memory; + } + + ret = __cpu_sys_clock_nanosleep( clockid, flags, &req, + ( flags & __CPU_TIMER_ABSTIME ) || rem_ptr == CPU_NULL_PTR ? + (struct __cpu_timespec *)0 : &rem ); + + if( ret == -(__mpu_int64_t)__MCPU_EINTR && !( flags & __CPU_TIMER_ABSTIME ) && + rem_ptr != CPU_NULL_PTR ) + { + error = __cpu_timespec_copyout( rem_ptr, &rem ); + if( error ) return( __cpu_syscall_error( error ) ); + } + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_clock_nanosleep() */ + + +__mpu_uint64_t +__cpu_SYS_fsync( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 1 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_fsync( host_fd ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_fsync() */ + + +__mpu_uint64_t +__cpu_SYS_truncate( __mpu_uint32_t sysno ) +{ + char *path = (char *)0; + __mpu_uint64_t bits; + __mpu_int64_t length; + __mpu_int64_t ret; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + error = __cpu_copyinstr( &path, __cpu_syscall_arg( 0 ) ); + if( error ) return( __cpu_syscall_error( error ) ); + + bits = __cpu_syscall_arg( 1 ); + memcpy( &length, &bits, sizeof( length ) ); + + ret = __cpu_sys_truncate( pcctx->_cwd_fd, path, length ); + free( path ); + + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_truncate() */ + + +__mpu_uint64_t +__cpu_SYS_ftruncate( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_uint64_t bits; + __mpu_int64_t length; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + bits = __cpu_syscall_arg( 1 ); + memcpy( &length, &bits, sizeof( length ) ); + + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_ftruncate( host_fd, length ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_ftruncate() */ + + +__mpu_uint64_t +__cpu_SYS_fchmod( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 2 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_fchmod( host_fd, __cpu_syscall_arg( 1 ) ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_fchmod() */ + + +__mpu_uint64_t +__cpu_SYS_fchown( __mpu_uint32_t sysno ) +{ + __mpu_uint64_t fd; + __mpu_int64_t ret; + int host_fd; + int error; + + (void)sysno; + + if( __cpu_syscall_nargs() != 3 ) return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + fd = __cpu_syscall_arg( 0 ); + error = __cpu_fd_get( fd, &host_fd ); + if( error ) return( __cpu_syscall_error( error ) ); + + ret = __cpu_sys_fchown( host_fd, __cpu_syscall_arg( 1 ), + __cpu_syscall_arg( 2 ) ); + return( (__mpu_uint64_t)ret ); + +} /* End of __cpu_SYS_fchown() */ + + +static const __cpu_syscall_handler_t __syscalls[] = +{ + [__NR_brk] = __cpu_SYS_brk, + [__NR_gettimeofday] = __cpu_SYS_gettimeofday, + [__NR_clock_getres] = __cpu_SYS_clock_getres, + [__NR_clock_gettime] = __cpu_SYS_clock_gettime, + [__NR_nanosleep] = __cpu_SYS_nanosleep, + [__NR_clock_nanosleep] = __cpu_SYS_clock_nanosleep, + [__NR_read] = __cpu_SYS_read, + [__NR_write] = __cpu_SYS_write, + [__NR_lseek] = __cpu_SYS_lseek, + [__NR_open] = __cpu_SYS_open, + [__NR_creat] = __cpu_SYS_creat, + [__NR_close] = __cpu_SYS_close, + [__NR_link] = __cpu_SYS_link, + [__NR_unlink] = __cpu_SYS_unlink, + [__NR_chdir] = __cpu_SYS_chdir, + [__NR_mkdir] = __cpu_SYS_mkdir, + [__NR_chmod] = __cpu_SYS_chmod, + [__NR_chown] = __cpu_SYS_chown, + [__NR_access] = __cpu_SYS_access, + [__NR_rename] = __cpu_SYS_rename, + [__NR_rmdir] = __cpu_SYS_rmdir, + [__NR_symlink] = __cpu_SYS_symlink, + [__NR_readlink] = __cpu_SYS_readlink, + [__NR_stat] = __cpu_SYS_stat, + [__NR_fstat] = __cpu_SYS_fstat, + [__NR_lstat] = __cpu_SYS_lstat, + [__NR_fcntl] = __cpu_SYS_fcntl, + [__NR_pipe2] = __cpu_SYS_pipe2, + [__NR_getdents] = __cpu_SYS_getdents, + [__NR_select] = __cpu_SYS_select, + [__NR_fsync] = __cpu_SYS_fsync, + [__NR_truncate] = __cpu_SYS_truncate, + [__NR_ftruncate] = __cpu_SYS_ftruncate, + [__NR_fchmod] = __cpu_SYS_fchmod, + [__NR_fchown] = __cpu_SYS_fchown, + [__NR_fchdir] = __cpu_SYS_fchdir, + [__NR_dup] = __cpu_SYS_dup, + [__NR_dup2] = __cpu_SYS_dup2, +}; + + +__mpu_uint64_t +__cpu_syscall_dispatch( void ) +{ + __mpu_uint32_t sysno; + + sysno = __cpu_data_get_u32( __cpu_reg_ptr( 32, CPU_SYSCALL_SYSNO_ADDR ) ); + + if( __cpu_syscall_nargs() > CPU_SYSCALL_MAX_ARGS ) + return( __cpu_syscall_error( __MCPU_EINVAL ) ); + + if( sysno >= sizeof( __syscalls ) / sizeof( __syscalls[0] ) || + __syscalls[sysno] == (__cpu_syscall_handler_t)0 ) + return( __cpu_syscall_error( __MCPU_ENOSYS ) ); + + return( __syscalls[sysno]( sysno ) ); + +} /* End of __cpu_syscall_dispatch() */ diff --git a/cpu/cpu-syscalls.h b/cpu/cpu-syscalls.h new file mode 100644 index 0000000..636c0e7 --- /dev/null +++ b/cpu/cpu-syscalls.h @@ -0,0 +1,59 @@ +/*************************************************************** + __CPU_SYSCALLS.H + + This file contains CPU SYSCALL operation functions & data + declarations. + + PART OF : CPU - library . + + USAGE : Internal only . + + NOTE : Include "libmcpu.h" before this FILE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_SYSCALLS_H +#define __CPU_SYSCALLS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef __mpu_uint64_t (*__cpu_syscall_handler_t)( __mpu_uint32_t sysno ); + +extern void __cpu_fd_context_init( void ); +extern void __cpu_fd_context_free( void ); +extern void __cpu_cwd_context_init( void ); +extern void __cpu_cwd_context_free( void ); + +extern __mpu_uint64_t __cpu_SYS_brk( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_gettimeofday( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_clock_getres( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_clock_gettime( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_nanosleep( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_clock_nanosleep( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_open ( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_close( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_dup ( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_dup2 ( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_read ( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_write( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_lseek( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_chdir( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_fchdir( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_stat ( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_fstat( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_lstat( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_fcntl( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_SYS_pipe2( __mpu_uint32_t sysno ); +extern __mpu_uint64_t __cpu_syscall_dispatch( void ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* __CPU_SYSCALLS_H */ diff --git a/cpu/cpu-sysdeps.h b/cpu/cpu-sysdeps.h new file mode 100644 index 0000000..81bb926 --- /dev/null +++ b/cpu/cpu-sysdeps.h @@ -0,0 +1,288 @@ +/*************************************************************** + __CPU_SYSDEPS.H + + This file contains HOST-dependent system call wrapper + declarations and internal MCPU syscall ABI constants. + + PART OF : CPU - library . + + USAGE : Internal only . + + NOTE : Include "libmcpu.h" before this FILE . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_SYSDEPS_H +#define __CPU_SYSDEPS_H + +#include <cpu-errno.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* MCPU open(2) ABI. These values are HOST-independent. */ +#define __CPU_O_ACCMODE 00000003U +#define __CPU_O_RDONLY 00000000U +#define __CPU_O_WRONLY 00000001U +#define __CPU_O_RDWR 00000002U +#define __CPU_O_CREAT 00000100U +#define __CPU_O_EXCL 00000200U +#define __CPU_O_NOCTTY 00000400U +#define __CPU_O_TRUNC 00001000U +#define __CPU_O_APPEND 00002000U +#define __CPU_O_NONBLOCK 00004000U + +#define __CPU_O_SUPPORTED ( __CPU_O_ACCMODE | __CPU_O_CREAT | __CPU_O_EXCL | \ + __CPU_O_NOCTTY | __CPU_O_TRUNC | __CPU_O_APPEND | \ + __CPU_O_NONBLOCK ) + +/* MCPU fcntl(2) ABI. Commands and descriptor flags are guest values. */ +#define __CPU_F_DUPFD 0U +#define __CPU_F_GETFD 1U +#define __CPU_F_SETFD 2U +#define __CPU_F_GETFL 3U +#define __CPU_F_SETFL 4U + +#define __CPU_FD_CLOEXEC 1U +#define __CPU_FD_SUPPORTED __CPU_FD_CLOEXEC + +/* F_SETFL accepts access-mode bits for the conventional GETFL/SETFL cycle, + but only APPEND and NONBLOCK are mutable file-status flags. */ +#define __CPU_F_SETFL_SUPPORTED \ + ( __CPU_O_ACCMODE | __CPU_O_APPEND | __CPU_O_NONBLOCK ) + +/* MCPU pipe2(2) ABI. O_CLOEXEC is guest descriptor state; only + O_NONBLOCK is translated to a HOST file-status flag. */ +#define __CPU_O_CLOEXEC 02000000U +#define __CPU_PIPE2_SUPPORTED ( __CPU_O_NONBLOCK | __CPU_O_CLOEXEC ) +#define __CPU_PIPEFD_SIZE 16U +#define __CPU_PIPEFD_READ 0U +#define __CPU_PIPEFD_WRITE 8U + +/* MCPU mode bits used by open(2) when O_CREAT is present. */ +#define __CPU_S_ISUID 00004000U +#define __CPU_S_ISGID 00002000U +#define __CPU_S_ISVTX 00001000U +#define __CPU_S_IRUSR 00000400U +#define __CPU_S_IWUSR 00000200U +#define __CPU_S_IXUSR 00000100U +#define __CPU_S_IRGRP 00000040U +#define __CPU_S_IWGRP 00000020U +#define __CPU_S_IXGRP 00000010U +#define __CPU_S_IROTH 00000004U +#define __CPU_S_IWOTH 00000002U +#define __CPU_S_IXOTH 00000001U +#define __CPU_S_SUPPORTED 00007777U + +/* MCPU chown(2) ABI. UINT64_MAX means "leave this ID unchanged". */ +#define __CPU_ID_NO_CHANGE ((__mpu_uint64_t)-1) + +#define __CPU_SEEK_SET 0U +#define __CPU_SEEK_CUR 1U +#define __CPU_SEEK_END 2U + +/* MCPU access(2) ABI. These values are HOST-independent. */ +#define __CPU_F_OK 0U +#define __CPU_X_OK 1U +#define __CPU_W_OK 2U +#define __CPU_R_OK 4U +#define __CPU_ACCESS_SUPPORTED (__CPU_X_OK | __CPU_W_OK | __CPU_R_OK) + +/* MCPU time ABI. All time values use signed 64-bit seconds and a separate + signed 64-bit fractional field. Clock IDs are HOST-independent. */ +#define __CPU_CLOCK_REALTIME 0U +#define __CPU_CLOCK_MONOTONIC 1U +#define __CPU_TIMER_ABSTIME 1U + +#define __CPU_TIMEVAL_SIZE 16U +#define __CPU_TIMEVAL_TV_SEC 0U +#define __CPU_TIMEVAL_TV_USEC 8U + +#define __CPU_TIMESPEC_SIZE 16U +#define __CPU_TIMESPEC_TV_SEC 0U +#define __CPU_TIMESPEC_TV_NSEC 8U + +struct __cpu_timeval +{ + __mpu_int64_t tv_sec; + __mpu_int64_t tv_usec; +}; + +struct __cpu_timespec +{ + __mpu_int64_t tv_sec; + __mpu_int64_t tv_nsec; +}; + +/* MCPU select(2) ABI. Descriptor sets contain four target-order u64 + words, for a total of 256 guest descriptor bits. The timeout is input-only. */ +#define __CPU_FD_SETSIZE 256U +#define __CPU_FD_SET_WORDS 4U +#define __CPU_FD_SET_SIZE 32U + +#define __CPU_SELECT_READ 0x01U +#define __CPU_SELECT_WRITE 0x02U +#define __CPU_SELECT_EXCEPT 0x04U + +struct __cpu_select_entry +{ + unsigned int guest_fd; + int host_fd; + unsigned int events; + unsigned int revents; +}; + +/* MCPU stat(2) ABI. The structure is always exactly 128 bytes. */ +#define __CPU_STAT_SIZE 128U +#define __CPU_STAT_ST_DEV 0U +#define __CPU_STAT_ST_INO 8U +#define __CPU_STAT_ST_MODE 16U +#define __CPU_STAT_ST_NLINK 24U +#define __CPU_STAT_ST_UID 32U +#define __CPU_STAT_ST_GID 40U +#define __CPU_STAT_ST_RDEV 48U +#define __CPU_STAT_ST_SIZE 56U +#define __CPU_STAT_ST_BLKSIZE 64U +#define __CPU_STAT_ST_BLOCKS 72U +#define __CPU_STAT_ST_ATIME_SEC 80U +#define __CPU_STAT_ST_ATIME_NSEC 88U +#define __CPU_STAT_ST_MTIME_SEC 96U +#define __CPU_STAT_ST_MTIME_NSEC 104U +#define __CPU_STAT_ST_CTIME_SEC 112U +#define __CPU_STAT_ST_CTIME_NSEC 120U + +#define __CPU_S_IFMT 000170000U +#define __CPU_S_IFIFO 000010000U +#define __CPU_S_IFCHR 000020000U +#define __CPU_S_IFDIR 000040000U +#define __CPU_S_IFBLK 000060000U +#define __CPU_S_IFREG 000100000U +#define __CPU_S_IFLNK 000120000U +#define __CPU_S_IFSOCK 000140000U + +/* MCPU getdents(2) ABI. Records are fixed at 288 bytes. */ +#define __CPU_DIRENT_SIZE 288U +#define __CPU_DIRENT_D_INO 0U +#define __CPU_DIRENT_D_OFF 8U +#define __CPU_DIRENT_D_RECLEN 16U +#define __CPU_DIRENT_D_TYPE 24U +#define __CPU_DIRENT_D_NAME 32U +#define __CPU_DIRENT_NAME_SIZE 256U + +#define __CPU_DT_UNKNOWN 0U +#define __CPU_DT_FIFO 1U +#define __CPU_DT_CHR 2U +#define __CPU_DT_DIR 4U +#define __CPU_DT_BLK 6U +#define __CPU_DT_REG 8U +#define __CPU_DT_LNK 10U +#define __CPU_DT_SOCK 12U + +struct __cpu_dirent +{ + __mpu_uint64_t d_ino; + __mpu_int64_t d_off; + __mpu_uint64_t d_reclen; + __mpu_uint64_t d_type; + unsigned char d_name[__CPU_DIRENT_NAME_SIZE]; +}; + +struct __cpu_stat +{ + __mpu_uint64_t st_dev; + __mpu_uint64_t st_ino; + __mpu_uint64_t st_mode; + __mpu_uint64_t st_nlink; + __mpu_uint64_t st_uid; + __mpu_uint64_t st_gid; + __mpu_uint64_t st_rdev; + __mpu_int64_t st_size; + __mpu_int64_t st_blksize; + __mpu_int64_t st_blocks; + __mpu_int64_t st_atime_sec; + __mpu_uint64_t st_atime_nsec; + __mpu_int64_t st_mtime_sec; + __mpu_uint64_t st_mtime_nsec; + __mpu_int64_t st_ctime_sec; + __mpu_uint64_t st_ctime_nsec; +}; + + + +/* Translate a positive HOST errno value into the stable positive MCPU errno + namespace. Zero maps to zero; unknown values map to __MCPU_EUNKNOWN. */ +extern int __cpu_errno_from_host( int errnum ); + +extern __mpu_int64_t __cpu_sys_cwd_init( void ); +extern __mpu_int64_t __cpu_sys_chdir( int dirfd, const char *path ); +extern __mpu_int64_t __cpu_sys_fchdir( int fd ); + +extern __mpu_int64_t __cpu_sys_open ( int dirfd, const char *path, + __mpu_uint64_t flags, + __mpu_uint64_t mode ); +extern __mpu_int64_t __cpu_sys_creat( int dirfd, const char *path, + __mpu_uint64_t mode ); +extern __mpu_int64_t __cpu_sys_close( int fd ); +extern __mpu_int64_t __cpu_sys_dup ( int fd ); +extern __mpu_int64_t __cpu_sys_read ( int fd, void *buf, __mpu_uint64_t count ); +extern __mpu_int64_t __cpu_sys_write( int fd, const void *buf, __mpu_uint64_t count ); +extern __mpu_int64_t __cpu_sys_lseek( int fd, __mpu_int64_t offset, + __mpu_uint64_t whence ); +extern __mpu_int64_t __cpu_sys_link ( int dirfd, const char *oldpath, + const char *newpath ); +extern __mpu_int64_t __cpu_sys_unlink( int dirfd, const char *path ); +extern __mpu_int64_t __cpu_sys_rename( int dirfd, const char *oldpath, + const char *newpath ); +extern __mpu_int64_t __cpu_sys_symlink( int dirfd, const char *target, + const char *linkpath ); +extern __mpu_int64_t __cpu_sys_readlink( int dirfd, const char *path, + void *buf, __mpu_uint64_t count ); +extern __mpu_int64_t __cpu_sys_mkdir ( int dirfd, const char *path, + __mpu_uint64_t mode ); +extern __mpu_int64_t __cpu_sys_chmod ( int dirfd, const char *path, + __mpu_uint64_t mode ); +extern __mpu_int64_t __cpu_sys_chown ( int dirfd, const char *path, + __mpu_uint64_t uid, + __mpu_uint64_t gid ); +extern __mpu_int64_t __cpu_sys_rmdir ( int dirfd, const char *path ); +extern __mpu_int64_t __cpu_sys_access( int dirfd, const char *path, + __mpu_uint64_t mode ); + +extern __mpu_int64_t __cpu_sys_stat ( int dirfd, const char *path, + struct __cpu_stat *st ); +extern __mpu_int64_t __cpu_sys_fstat( int fd, struct __cpu_stat *st ); +extern __mpu_int64_t __cpu_sys_lstat( int dirfd, const char *path, + struct __cpu_stat *st ); +extern __mpu_int64_t __cpu_sys_getdents( int fd, struct __cpu_dirent *dirp, + __mpu_uint64_t count ); +extern __mpu_int64_t __cpu_sys_fsync( int fd ); +extern __mpu_int64_t __cpu_sys_truncate( int dirfd, const char *path, + __mpu_int64_t length ); +extern __mpu_int64_t __cpu_sys_ftruncate( int fd, __mpu_int64_t length ); +extern __mpu_int64_t __cpu_sys_fchmod( int fd, __mpu_uint64_t mode ); +extern __mpu_int64_t __cpu_sys_fchown( int fd, __mpu_uint64_t uid, + __mpu_uint64_t gid ); +extern __mpu_int64_t __cpu_sys_fcntl( int fd, __mpu_uint64_t cmd, + __mpu_uint64_t arg ); +extern __mpu_int64_t __cpu_sys_pipe2( int pipefd[2], __mpu_uint64_t flags ); +extern __mpu_int64_t __cpu_sys_select( struct __cpu_select_entry *entries, + __mpu_uint64_t count, + const struct __cpu_timeval *timeout ); +extern __mpu_int64_t __cpu_sys_clock_gettime( __mpu_uint64_t clockid, + struct __cpu_timespec *tp ); +extern __mpu_int64_t __cpu_sys_clock_getres( __mpu_uint64_t clockid, + struct __cpu_timespec *res ); +extern __mpu_int64_t __cpu_sys_clock_nanosleep( __mpu_uint64_t clockid, + __mpu_uint64_t flags, + const struct __cpu_timespec *req, + struct __cpu_timespec *rem ); + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* __CPU_SYSDEPS_H */ diff --git a/cpu/sysdeps/README b/cpu/sysdeps/README new file mode 100644 index 0000000..461226e --- /dev/null +++ b/cpu/sysdeps/README @@ -0,0 +1,19 @@ +LibMCPU HOST sysdeps +==================== + +This tree is the HOST-dependent adaptation layer for MCPU system-call wrappers. +configure builds the ordered `sysnames` search list from the canonical HOST, +architecture preconfigure fragments, the ABI/data model and Implies inheritance. +cpu/Makefile.am uses that ordered list as the GNU make `vpath %.c` search path, +so the first matching basename is the selected source. + +The search order is intentionally from the most specific implementation to the +most generic implementation. `cpu/sysdeps/generic` is always last. A generic +directory does not by itself make an otherwise unknown HOST supported. + +Starting with LibMCPU 0.3.25 raw HOST errno values must not escape this layer. +The inherited `sys-errno.c` implementation provides `__cpu_errno_from_host()`; +HOST syscall wrappers translate a real HOST failure into the stable MCPU errno +namespace before returning to common CPU code. Errors created by the MCPU ABI +itself use `__MCPU_E*` constants directly and are not sent through the HOST +translator. diff --git a/cpu/sysdeps/generic/README b/cpu/sysdeps/generic/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/generic/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/i386/configure-fragment b/cpu/sysdeps/i386/configure-fragment new file mode 100755 index 0000000..a64629c --- /dev/null +++ b/cpu/sysdeps/i386/configure-fragment @@ -0,0 +1,8 @@ +#!/bin/sh + +# Regenerate this directory's sourced preconfigure fragment from preconfigure.ac. +# Run from the architecture sysdeps directory. +autoconf -Wno-syntax -I ../../.. -o preconfigure preconfigure.ac || exit 1 +chmod a-x preconfigure +rm -f preconfigure~ +rm -rf autom4te.cache diff --git a/cpu/sysdeps/i386/i586/README b/cpu/sysdeps/i386/i586/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/i386/i586/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/i386/i686/README b/cpu/sysdeps/i386/i686/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/i386/i686/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/i386/i786/Implies b/cpu/sysdeps/i386/i786/Implies new file mode 100644 index 0000000..1cd29f6 --- /dev/null +++ b/cpu/sysdeps/i386/i786/Implies @@ -0,0 +1,2 @@ +# The PPro and PII cores are mostly the same. +i386/i686 diff --git a/cpu/sysdeps/i386/preconfigure b/cpu/sysdeps/i386/preconfigure new file mode 100644 index 0000000..43a17f4 --- /dev/null +++ b/cpu/sysdeps/i386/preconfigure @@ -0,0 +1,12 @@ +# This file is generated from preconfigure.ac by Autoconf. DO NOT EDIT! + +# Local preconfigure fragment for cpu/sysdeps/i386. + +case "$machine" in +i456786) + base_machine=i386 + machine=i386/$machine + MCPU_HOST_DATA_MODEL=ILP32 + ;; +esac + diff --git a/cpu/sysdeps/i386/preconfigure.ac b/cpu/sysdeps/i386/preconfigure.ac new file mode 100644 index 0000000..cd419ee --- /dev/null +++ b/cpu/sysdeps/i386/preconfigure.ac @@ -0,0 +1,11 @@ +dnl See acsite.m4 in the top-level source directory. +MCPU_PROVIDES +# Local preconfigure fragment for cpu/sysdeps/i386. + +case "$machine" in +i[4567]86) + base_machine=i386 + machine=i386/$machine + MCPU_HOST_DATA_MODEL=ILP32 + ;; +esac diff --git a/cpu/sysdeps/powerpc/configure-fragment b/cpu/sysdeps/powerpc/configure-fragment new file mode 100755 index 0000000..a64629c --- /dev/null +++ b/cpu/sysdeps/powerpc/configure-fragment @@ -0,0 +1,8 @@ +#!/bin/sh + +# Regenerate this directory's sourced preconfigure fragment from preconfigure.ac. +# Run from the architecture sysdeps directory. +autoconf -Wno-syntax -I ../../.. -o preconfigure preconfigure.ac || exit 1 +chmod a-x preconfigure +rm -f preconfigure~ +rm -rf autom4te.cache diff --git a/cpu/sysdeps/powerpc/powerpc32/README b/cpu/sysdeps/powerpc/powerpc32/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/powerpc/powerpc32/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/powerpc/powerpc64/README b/cpu/sysdeps/powerpc/powerpc64/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/powerpc/powerpc64/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/powerpc/powerpc64le/README b/cpu/sysdeps/powerpc/powerpc64le/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/powerpc/powerpc64le/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/powerpc/preconfigure b/cpu/sysdeps/powerpc/preconfigure new file mode 100644 index 0000000..ac680cc --- /dev/null +++ b/cpu/sysdeps/powerpc/preconfigure @@ -0,0 +1,22 @@ +# This file is generated from preconfigure.ac by Autoconf. DO NOT EDIT! + +# Local preconfigure fragment for cpu/sysdeps/powerpc. + +case "$machine" in +powerpc64le) + base_machine=powerpc + machine=powerpc/powerpc64le + MCPU_HOST_DATA_MODEL=LP64 + ;; +powerpc64*) + base_machine=powerpc + machine=powerpc/powerpc64 + MCPU_HOST_DATA_MODEL=LP64 + ;; +powerpc*) + base_machine=powerpc + machine=powerpc/powerpc32 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +esac + diff --git a/cpu/sysdeps/powerpc/preconfigure.ac b/cpu/sysdeps/powerpc/preconfigure.ac new file mode 100644 index 0000000..9fd020f --- /dev/null +++ b/cpu/sysdeps/powerpc/preconfigure.ac @@ -0,0 +1,21 @@ +dnl See acsite.m4 in the top-level source directory. +MCPU_PROVIDES +# Local preconfigure fragment for cpu/sysdeps/powerpc. + +case "$machine" in +powerpc64le) + base_machine=powerpc + machine=powerpc/powerpc64le + MCPU_HOST_DATA_MODEL=LP64 + ;; +powerpc64*) + base_machine=powerpc + machine=powerpc/powerpc64 + MCPU_HOST_DATA_MODEL=LP64 + ;; +powerpc*) + base_machine=powerpc + machine=powerpc/powerpc32 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +esac diff --git a/cpu/sysdeps/sparc/configure-fragment b/cpu/sysdeps/sparc/configure-fragment new file mode 100755 index 0000000..a64629c --- /dev/null +++ b/cpu/sysdeps/sparc/configure-fragment @@ -0,0 +1,8 @@ +#!/bin/sh + +# Regenerate this directory's sourced preconfigure fragment from preconfigure.ac. +# Run from the architecture sysdeps directory. +autoconf -Wno-syntax -I ../../.. -o preconfigure preconfigure.ac || exit 1 +chmod a-x preconfigure +rm -f preconfigure~ +rm -rf autom4te.cache diff --git a/cpu/sysdeps/sparc/preconfigure b/cpu/sysdeps/sparc/preconfigure new file mode 100644 index 0000000..ecf135c --- /dev/null +++ b/cpu/sysdeps/sparc/preconfigure @@ -0,0 +1,57 @@ +# This file is generated from preconfigure.ac by Autoconf. DO NOT EDIT! + +# Local preconfigure fragment for cpu/sysdeps/sparc. + +case "$machine" in +sparc | sparcv67) + base_machine=sparc + machine=sparc/sparc32 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8 | supersparc | hypersparc) + base_machine=sparc + machine=sparc/sparc32/sparcv8 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8plus | sparcv8plusa | sparcv9) + base_machine=sparc + machine=sparc/sparc32/sparcv9 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8plusb | sparcv9b) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9b + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv9v) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9v + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv9v2) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9v2 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparc64) + base_machine=sparc + machine=sparc/sparc64 + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64b) + base_machine=sparc + machine=sparc/sparc64/sparcv9b + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64v) + base_machine=sparc + machine=sparc/sparc64/sparcv9v + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64v2) + base_machine=sparc + machine=sparc/sparc64/sparcv9v2 + MCPU_HOST_DATA_MODEL=LP64 + ;; +esac + diff --git a/cpu/sysdeps/sparc/preconfigure.ac b/cpu/sysdeps/sparc/preconfigure.ac new file mode 100644 index 0000000..3a3361b --- /dev/null +++ b/cpu/sysdeps/sparc/preconfigure.ac @@ -0,0 +1,56 @@ +dnl See acsite.m4 in the top-level source directory. +MCPU_PROVIDES +# Local preconfigure fragment for cpu/sysdeps/sparc. + +case "$machine" in +sparc | sparcv[67]) + base_machine=sparc + machine=sparc/sparc32 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8 | supersparc | hypersparc) + base_machine=sparc + machine=sparc/sparc32/sparcv8 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8plus | sparcv8plusa | sparcv9) + base_machine=sparc + machine=sparc/sparc32/sparcv9 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv8plusb | sparcv9b) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9b + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv9v) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9v + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparcv9v2) + base_machine=sparc + machine=sparc/sparc32/sparcv9/sparcv9v2 + MCPU_HOST_DATA_MODEL=ILP32 + ;; +sparc64) + base_machine=sparc + machine=sparc/sparc64 + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64b) + base_machine=sparc + machine=sparc/sparc64/sparcv9b + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64v) + base_machine=sparc + machine=sparc/sparc64/sparcv9v + MCPU_HOST_DATA_MODEL=LP64 + ;; +sparc64v2) + base_machine=sparc + machine=sparc/sparc64/sparcv9v2 + MCPU_HOST_DATA_MODEL=LP64 + ;; +esac diff --git a/cpu/sysdeps/unix/sys-errno.c b/cpu/sysdeps/unix/sys-errno.c new file mode 100644 index 0000000..ffcbb1d --- /dev/null +++ b/cpu/sysdeps/unix/sys-errno.c @@ -0,0 +1,442 @@ +/*************************************************************** + SYS-ERRNO.C + + This file contains Linux/x86_64/LP64 HOST errno to MCPU + errno translation. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +int +__cpu_errno_from_host( int errnum ) +{ + if( errnum == 0 ) return( 0 ); + if( errnum < 0 ) return( __MCPU_EUNKNOWN ); + +#ifdef EPERM + if( errnum == EPERM ) return( __MCPU_EPERM ); +#endif +#ifdef ENOENT + if( errnum == ENOENT ) return( __MCPU_ENOENT ); +#endif +#ifdef ESRCH + if( errnum == ESRCH ) return( __MCPU_ESRCH ); +#endif +#ifdef EINTR + if( errnum == EINTR ) return( __MCPU_EINTR ); +#endif +#ifdef EIO + if( errnum == EIO ) return( __MCPU_EIO ); +#endif +#ifdef ENXIO + if( errnum == ENXIO ) return( __MCPU_ENXIO ); +#endif +#ifdef E2BIG + if( errnum == E2BIG ) return( __MCPU_E2BIG ); +#endif +#ifdef ENOEXEC + if( errnum == ENOEXEC ) return( __MCPU_ENOEXEC ); +#endif +#ifdef EBADF + if( errnum == EBADF ) return( __MCPU_EBADF ); +#endif +#ifdef ECHILD + if( errnum == ECHILD ) return( __MCPU_ECHILD ); +#endif +#ifdef EAGAIN + if( errnum == EAGAIN ) return( __MCPU_EAGAIN ); +#endif +#ifdef ENOMEM + if( errnum == ENOMEM ) return( __MCPU_ENOMEM ); +#endif +#ifdef EACCES + if( errnum == EACCES ) return( __MCPU_EACCES ); +#endif +#ifdef EFAULT + if( errnum == EFAULT ) return( __MCPU_EFAULT ); +#endif +#ifdef ENOTBLK + if( errnum == ENOTBLK ) return( __MCPU_ENOTBLK ); +#endif +#ifdef EBUSY + if( errnum == EBUSY ) return( __MCPU_EBUSY ); +#endif +#ifdef EEXIST + if( errnum == EEXIST ) return( __MCPU_EEXIST ); +#endif +#ifdef EXDEV + if( errnum == EXDEV ) return( __MCPU_EXDEV ); +#endif +#ifdef ENODEV + if( errnum == ENODEV ) return( __MCPU_ENODEV ); +#endif +#ifdef ENOTDIR + if( errnum == ENOTDIR ) return( __MCPU_ENOTDIR ); +#endif +#ifdef EISDIR + if( errnum == EISDIR ) return( __MCPU_EISDIR ); +#endif +#ifdef EINVAL + if( errnum == EINVAL ) return( __MCPU_EINVAL ); +#endif +#ifdef ENFILE + if( errnum == ENFILE ) return( __MCPU_ENFILE ); +#endif +#ifdef EMFILE + if( errnum == EMFILE ) return( __MCPU_EMFILE ); +#endif +#ifdef ENOTTY + if( errnum == ENOTTY ) return( __MCPU_ENOTTY ); +#endif +#ifdef ETXTBSY + if( errnum == ETXTBSY ) return( __MCPU_ETXTBSY ); +#endif +#ifdef EFBIG + if( errnum == EFBIG ) return( __MCPU_EFBIG ); +#endif +#ifdef ENOSPC + if( errnum == ENOSPC ) return( __MCPU_ENOSPC ); +#endif +#ifdef ESPIPE + if( errnum == ESPIPE ) return( __MCPU_ESPIPE ); +#endif +#ifdef EROFS + if( errnum == EROFS ) return( __MCPU_EROFS ); +#endif +#ifdef EMLINK + if( errnum == EMLINK ) return( __MCPU_EMLINK ); +#endif +#ifdef EPIPE + if( errnum == EPIPE ) return( __MCPU_EPIPE ); +#endif +#ifdef EDOM + if( errnum == EDOM ) return( __MCPU_EDOM ); +#endif +#ifdef ERANGE + if( errnum == ERANGE ) return( __MCPU_ERANGE ); +#endif +#ifdef EDEADLK + if( errnum == EDEADLK ) return( __MCPU_EDEADLK ); +#endif +#ifdef ENAMETOOLONG + if( errnum == ENAMETOOLONG ) return( __MCPU_ENAMETOOLONG ); +#endif +#ifdef ENOLCK + if( errnum == ENOLCK ) return( __MCPU_ENOLCK ); +#endif +#ifdef ENOSYS + if( errnum == ENOSYS ) return( __MCPU_ENOSYS ); +#endif +#ifdef ENOTEMPTY + if( errnum == ENOTEMPTY ) return( __MCPU_ENOTEMPTY ); +#endif +#ifdef ELOOP + if( errnum == ELOOP ) return( __MCPU_ELOOP ); +#endif +#ifdef ENOMSG + if( errnum == ENOMSG ) return( __MCPU_ENOMSG ); +#endif +#ifdef EIDRM + if( errnum == EIDRM ) return( __MCPU_EIDRM ); +#endif +#ifdef ECHRNG + if( errnum == ECHRNG ) return( __MCPU_ECHRNG ); +#endif +#ifdef EL2NSYNC + if( errnum == EL2NSYNC ) return( __MCPU_EL2NSYNC ); +#endif +#ifdef EL3HLT + if( errnum == EL3HLT ) return( __MCPU_EL3HLT ); +#endif +#ifdef EL3RST + if( errnum == EL3RST ) return( __MCPU_EL3RST ); +#endif +#ifdef ELNRNG + if( errnum == ELNRNG ) return( __MCPU_ELNRNG ); +#endif +#ifdef EUNATCH + if( errnum == EUNATCH ) return( __MCPU_EUNATCH ); +#endif +#ifdef ENOCSI + if( errnum == ENOCSI ) return( __MCPU_ENOCSI ); +#endif +#ifdef EL2HLT + if( errnum == EL2HLT ) return( __MCPU_EL2HLT ); +#endif +#ifdef EBADE + if( errnum == EBADE ) return( __MCPU_EBADE ); +#endif +#ifdef EBADR + if( errnum == EBADR ) return( __MCPU_EBADR ); +#endif +#ifdef EXFULL + if( errnum == EXFULL ) return( __MCPU_EXFULL ); +#endif +#ifdef ENOANO + if( errnum == ENOANO ) return( __MCPU_ENOANO ); +#endif +#ifdef EBADRQC + if( errnum == EBADRQC ) return( __MCPU_EBADRQC ); +#endif +#ifdef EBADSLT + if( errnum == EBADSLT ) return( __MCPU_EBADSLT ); +#endif +#ifdef EBFONT + if( errnum == EBFONT ) return( __MCPU_EBFONT ); +#endif +#ifdef ENOSTR + if( errnum == ENOSTR ) return( __MCPU_ENOSTR ); +#endif +#ifdef ENODATA + if( errnum == ENODATA ) return( __MCPU_ENODATA ); +#endif +#ifdef ETIME + if( errnum == ETIME ) return( __MCPU_ETIME ); +#endif +#ifdef ENOSR + if( errnum == ENOSR ) return( __MCPU_ENOSR ); +#endif +#ifdef ENONET + if( errnum == ENONET ) return( __MCPU_ENONET ); +#endif +#ifdef ENOPKG + if( errnum == ENOPKG ) return( __MCPU_ENOPKG ); +#endif +#ifdef EREMOTE + if( errnum == EREMOTE ) return( __MCPU_EREMOTE ); +#endif +#ifdef ENOLINK + if( errnum == ENOLINK ) return( __MCPU_ENOLINK ); +#endif +#ifdef EADV + if( errnum == EADV ) return( __MCPU_EADV ); +#endif +#ifdef ESRMNT + if( errnum == ESRMNT ) return( __MCPU_ESRMNT ); +#endif +#ifdef ECOMM + if( errnum == ECOMM ) return( __MCPU_ECOMM ); +#endif +#ifdef EPROTO + if( errnum == EPROTO ) return( __MCPU_EPROTO ); +#endif +#ifdef EMULTIHOP + if( errnum == EMULTIHOP ) return( __MCPU_EMULTIHOP ); +#endif +#ifdef EDOTDOT + if( errnum == EDOTDOT ) return( __MCPU_EDOTDOT ); +#endif +#ifdef EBADMSG + if( errnum == EBADMSG ) return( __MCPU_EBADMSG ); +#endif +#ifdef EOVERFLOW + if( errnum == EOVERFLOW ) return( __MCPU_EOVERFLOW ); +#endif +#ifdef ENOTUNIQ + if( errnum == ENOTUNIQ ) return( __MCPU_ENOTUNIQ ); +#endif +#ifdef EBADFD + if( errnum == EBADFD ) return( __MCPU_EBADFD ); +#endif +#ifdef EREMCHG + if( errnum == EREMCHG ) return( __MCPU_EREMCHG ); +#endif +#ifdef ELIBACC + if( errnum == ELIBACC ) return( __MCPU_ELIBACC ); +#endif +#ifdef ELIBBAD + if( errnum == ELIBBAD ) return( __MCPU_ELIBBAD ); +#endif +#ifdef ELIBSCN + if( errnum == ELIBSCN ) return( __MCPU_ELIBSCN ); +#endif +#ifdef ELIBMAX + if( errnum == ELIBMAX ) return( __MCPU_ELIBMAX ); +#endif +#ifdef ELIBEXEC + if( errnum == ELIBEXEC ) return( __MCPU_ELIBEXEC ); +#endif +#ifdef EILSEQ + if( errnum == EILSEQ ) return( __MCPU_EILSEQ ); +#endif +#ifdef ERESTART + if( errnum == ERESTART ) return( __MCPU_ERESTART ); +#endif +#ifdef ESTRPIPE + if( errnum == ESTRPIPE ) return( __MCPU_ESTRPIPE ); +#endif +#ifdef EUSERS + if( errnum == EUSERS ) return( __MCPU_EUSERS ); +#endif +#ifdef ENOTSOCK + if( errnum == ENOTSOCK ) return( __MCPU_ENOTSOCK ); +#endif +#ifdef EDESTADDRREQ + if( errnum == EDESTADDRREQ ) return( __MCPU_EDESTADDRREQ ); +#endif +#ifdef EMSGSIZE + if( errnum == EMSGSIZE ) return( __MCPU_EMSGSIZE ); +#endif +#ifdef EPROTOTYPE + if( errnum == EPROTOTYPE ) return( __MCPU_EPROTOTYPE ); +#endif +#ifdef ENOPROTOOPT + if( errnum == ENOPROTOOPT ) return( __MCPU_ENOPROTOOPT ); +#endif +#ifdef EPROTONOSUPPORT + if( errnum == EPROTONOSUPPORT ) return( __MCPU_EPROTONOSUPPORT ); +#endif +#ifdef ESOCKTNOSUPPORT + if( errnum == ESOCKTNOSUPPORT ) return( __MCPU_ESOCKTNOSUPPORT ); +#endif +#ifdef EOPNOTSUPP + if( errnum == EOPNOTSUPP ) return( __MCPU_EOPNOTSUPP ); +#endif +#ifdef EPFNOSUPPORT + if( errnum == EPFNOSUPPORT ) return( __MCPU_EPFNOSUPPORT ); +#endif +#ifdef EAFNOSUPPORT + if( errnum == EAFNOSUPPORT ) return( __MCPU_EAFNOSUPPORT ); +#endif +#ifdef EADDRINUSE + if( errnum == EADDRINUSE ) return( __MCPU_EADDRINUSE ); +#endif +#ifdef EADDRNOTAVAIL + if( errnum == EADDRNOTAVAIL ) return( __MCPU_EADDRNOTAVAIL ); +#endif +#ifdef ENETDOWN + if( errnum == ENETDOWN ) return( __MCPU_ENETDOWN ); +#endif +#ifdef ENETUNREACH + if( errnum == ENETUNREACH ) return( __MCPU_ENETUNREACH ); +#endif +#ifdef ENETRESET + if( errnum == ENETRESET ) return( __MCPU_ENETRESET ); +#endif +#ifdef ECONNABORTED + if( errnum == ECONNABORTED ) return( __MCPU_ECONNABORTED ); +#endif +#ifdef ECONNRESET + if( errnum == ECONNRESET ) return( __MCPU_ECONNRESET ); +#endif +#ifdef ENOBUFS + if( errnum == ENOBUFS ) return( __MCPU_ENOBUFS ); +#endif +#ifdef EISCONN + if( errnum == EISCONN ) return( __MCPU_EISCONN ); +#endif +#ifdef ENOTCONN + if( errnum == ENOTCONN ) return( __MCPU_ENOTCONN ); +#endif +#ifdef ESHUTDOWN + if( errnum == ESHUTDOWN ) return( __MCPU_ESHUTDOWN ); +#endif +#ifdef ETOOMANYREFS + if( errnum == ETOOMANYREFS ) return( __MCPU_ETOOMANYREFS ); +#endif +#ifdef ETIMEDOUT + if( errnum == ETIMEDOUT ) return( __MCPU_ETIMEDOUT ); +#endif +#ifdef ECONNREFUSED + if( errnum == ECONNREFUSED ) return( __MCPU_ECONNREFUSED ); +#endif +#ifdef EHOSTDOWN + if( errnum == EHOSTDOWN ) return( __MCPU_EHOSTDOWN ); +#endif +#ifdef EHOSTUNREACH + if( errnum == EHOSTUNREACH ) return( __MCPU_EHOSTUNREACH ); +#endif +#ifdef EALREADY + if( errnum == EALREADY ) return( __MCPU_EALREADY ); +#endif +#ifdef EINPROGRESS + if( errnum == EINPROGRESS ) return( __MCPU_EINPROGRESS ); +#endif +#ifdef ESTALE + if( errnum == ESTALE ) return( __MCPU_ESTALE ); +#endif +#ifdef EUCLEAN + if( errnum == EUCLEAN ) return( __MCPU_EUCLEAN ); +#endif +#ifdef ENOTNAM + if( errnum == ENOTNAM ) return( __MCPU_ENOTNAM ); +#endif +#ifdef ENAVAIL + if( errnum == ENAVAIL ) return( __MCPU_ENAVAIL ); +#endif +#ifdef EISNAM + if( errnum == EISNAM ) return( __MCPU_EISNAM ); +#endif +#ifdef EREMOTEIO + if( errnum == EREMOTEIO ) return( __MCPU_EREMOTEIO ); +#endif +#ifdef EDQUOT + if( errnum == EDQUOT ) return( __MCPU_EDQUOT ); +#endif +#ifdef ENOMEDIUM + if( errnum == ENOMEDIUM ) return( __MCPU_ENOMEDIUM ); +#endif +#ifdef EMEDIUMTYPE + if( errnum == EMEDIUMTYPE ) return( __MCPU_EMEDIUMTYPE ); +#endif +#ifdef ECANCELED + if( errnum == ECANCELED ) return( __MCPU_ECANCELED ); +#endif +#ifdef ENOKEY + if( errnum == ENOKEY ) return( __MCPU_ENOKEY ); +#endif +#ifdef EKEYEXPIRED + if( errnum == EKEYEXPIRED ) return( __MCPU_EKEYEXPIRED ); +#endif +#ifdef EKEYREVOKED + if( errnum == EKEYREVOKED ) return( __MCPU_EKEYREVOKED ); +#endif +#ifdef EKEYREJECTED + if( errnum == EKEYREJECTED ) return( __MCPU_EKEYREJECTED ); +#endif +#ifdef EOWNERDEAD + if( errnum == EOWNERDEAD ) return( __MCPU_EOWNERDEAD ); +#endif +#ifdef ENOTRECOVERABLE + if( errnum == ENOTRECOVERABLE ) return( __MCPU_ENOTRECOVERABLE ); +#endif +#ifdef ERFKILL + if( errnum == ERFKILL ) return( __MCPU_ERFKILL ); +#endif +#ifdef EHWPOISON + if( errnum == EHWPOISON ) return( __MCPU_EHWPOISON ); +#endif +#ifdef EFTYPE + if( errnum == EFTYPE ) return( __MCPU_EFTYPE ); +#endif +#ifdef EWOULDBLOCK + if( errnum == EWOULDBLOCK ) return( __MCPU_EWOULDBLOCK ); +#endif +#ifdef EDEADLOCK + if( errnum == EDEADLOCK ) return( __MCPU_EDEADLOCK ); +#endif +#ifdef ENOTSUP + if( errnum == ENOTSUP ) return( __MCPU_ENOTSUP ); +#endif +#ifdef EUNKNOWN + if( errnum == EUNKNOWN ) return( __MCPU_EUNKNOWN ); +#endif + + return( __MCPU_EUNKNOWN ); + +} /* End of __cpu_errno_from_host() */ diff --git a/cpu/sysdeps/unix/sysv/linux/i386/i686/README b/cpu/sysdeps/unix/sysv/linux/i386/i686/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/i386/i686/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/unix/sysv/linux/i386/i786/Implies b/cpu/sysdeps/unix/sysv/linux/i386/i786/Implies new file mode 100644 index 0000000..7cb7d9a --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/i386/i786/Implies @@ -0,0 +1,2 @@ +# The PPro and PII cores are mostly the same. +unix/sysv/linux/i386/i686 diff --git a/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc32/README b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc32/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc32/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64/README b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64le/README b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64le/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/powerpc/powerpc64le/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/README b/cpu/sysdeps/unix/sysv/linux/x86_64/64/README new file mode 100644 index 0000000..c28377c --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/README @@ -0,0 +1,8 @@ +Linux/x86_64/LP64 HOST sysdeps +============================== + +This directory contains the selected low-level syscall adapters for the +currently supported Linux/x86_64 LP64 HOST profile. The common UNIX errno +translator is inherited from `cpu/sysdeps/unix/sys-errno.c`; all real HOST +failures are translated to stable MCPU errno values before control returns to +the common CPU layer. diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-chown-linux.h b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-chown-linux.h new file mode 100644 index 0000000..7b39ae4 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-chown-linux.h @@ -0,0 +1,66 @@ +/*************************************************************** + CPU-CHOWN-LINUX.H + + This file contains Linux/x86_64/LP64 conversion helpers + for MCPU uid/gid values -> HOST uid_t/gid_t. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_CHOWN_LINUX_H +#define __CPU_CHOWN_LINUX_H + +#include <errno.h> +#include <sys/types.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_uid_to_host( __mpu_uint64_t uid, uid_t *host_uid ) +{ + uid_t u; + + if( !host_uid ) return( __MCPU_EINVAL ); + if( uid == __CPU_ID_NO_CHANGE ) + { + *host_uid = (uid_t)-1; + return( 0 ); + } + + u = (uid_t)uid; + if( (__mpu_uint64_t)u != uid ) return( __MCPU_EOVERFLOW ); + + *host_uid = u; + return( 0 ); + +} /* End of __cpu_uid_to_host() */ + + +static int +__cpu_gid_to_host( __mpu_uint64_t gid, gid_t *host_gid ) +{ + gid_t g; + + if( !host_gid ) return( __MCPU_EINVAL ); + if( gid == __CPU_ID_NO_CHANGE ) + { + *host_gid = (gid_t)-1; + return( 0 ); + } + + g = (gid_t)gid; + if( (__mpu_uint64_t)g != gid ) return( __MCPU_EOVERFLOW ); + + *host_gid = g; + return( 0 ); + +} /* End of __cpu_gid_to_host() */ + +#endif /* __CPU_CHOWN_LINUX_H */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-mode-linux.h b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-mode-linux.h new file mode 100644 index 0000000..a68e50c --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-mode-linux.h @@ -0,0 +1,53 @@ +/*************************************************************** + CPU-MODE-LINUX.H + + This file contains Linux/x86_64/LP64 conversion helper + for MCPU permission bits -> HOST mode_t. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_MODE_LINUX_H +#define __CPU_MODE_LINUX_H + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_mode_to_host( __mpu_uint64_t mode, mode_t *host_mode ) +{ + mode_t m = 0; + + if( !host_mode ) return( __MCPU_EINVAL ); + if( mode & ~(__mpu_uint64_t)__CPU_S_SUPPORTED ) return( __MCPU_EINVAL ); + + if( mode & __CPU_S_ISUID ) m |= S_ISUID; + if( mode & __CPU_S_ISGID ) m |= S_ISGID; +#ifdef S_ISVTX + if( mode & __CPU_S_ISVTX ) m |= S_ISVTX; +#endif + if( mode & __CPU_S_IRUSR ) m |= S_IRUSR; + if( mode & __CPU_S_IWUSR ) m |= S_IWUSR; + if( mode & __CPU_S_IXUSR ) m |= S_IXUSR; + if( mode & __CPU_S_IRGRP ) m |= S_IRGRP; + if( mode & __CPU_S_IWGRP ) m |= S_IWGRP; + if( mode & __CPU_S_IXGRP ) m |= S_IXGRP; + if( mode & __CPU_S_IROTH ) m |= S_IROTH; + if( mode & __CPU_S_IWOTH ) m |= S_IWOTH; + if( mode & __CPU_S_IXOTH ) m |= S_IXOTH; + + *host_mode = m; + return( 0 ); + +} /* End of __cpu_mode_to_host() */ + +#endif /* __CPU_MODE_LINUX_H */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-stat-linux.h b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-stat-linux.h new file mode 100644 index 0000000..58ac956 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-stat-linux.h @@ -0,0 +1,124 @@ +/*************************************************************** + CPU-STAT-LINUX.H + + This file contains Linux/x86_64/LP64 conversion helpers + for the HOST struct stat -> MCPU struct __cpu_stat ABI. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_STAT_LINUX_H +#define __CPU_STAT_LINUX_H + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_stat_mode( mode_t mode, __mpu_uint64_t *out ) +{ + __mpu_uint64_t m = 0; + + if( !out ) return( __MCPU_EFAULT ); + + switch( mode & S_IFMT ) + { + case S_IFIFO: m |= __CPU_S_IFIFO; break; + case S_IFCHR: m |= __CPU_S_IFCHR; break; + case S_IFDIR: m |= __CPU_S_IFDIR; break; + case S_IFBLK: m |= __CPU_S_IFBLK; break; + case S_IFREG: m |= __CPU_S_IFREG; break; + case S_IFLNK: m |= __CPU_S_IFLNK; break; +#ifdef S_IFSOCK + case S_IFSOCK: m |= __CPU_S_IFSOCK; break; +#endif + default: return( __MCPU_EOVERFLOW ); + } + + if( mode & S_ISUID ) m |= __CPU_S_ISUID; + if( mode & S_ISGID ) m |= __CPU_S_ISGID; +#ifdef S_ISVTX + if( mode & S_ISVTX ) m |= __CPU_S_ISVTX; +#endif + if( mode & S_IRUSR ) m |= __CPU_S_IRUSR; + if( mode & S_IWUSR ) m |= __CPU_S_IWUSR; + if( mode & S_IXUSR ) m |= __CPU_S_IXUSR; + if( mode & S_IRGRP ) m |= __CPU_S_IRGRP; + if( mode & S_IWGRP ) m |= __CPU_S_IWGRP; + if( mode & S_IXGRP ) m |= __CPU_S_IXGRP; + if( mode & S_IROTH ) m |= __CPU_S_IROTH; + if( mode & S_IWOTH ) m |= __CPU_S_IWOTH; + if( mode & S_IXOTH ) m |= __CPU_S_IXOTH; + + *out = m; + return( 0 ); + +} /* End of __cpu_stat_mode() */ + + +static int +__cpu_stat_from_host( struct __cpu_stat *dst, const struct stat *src ) +{ + int error; + + if( !dst || !src ) return( __MCPU_EFAULT ); + + dst->st_dev = (__mpu_uint64_t)src->st_dev; + if( (dev_t)dst->st_dev != src->st_dev ) return( __MCPU_EOVERFLOW ); + + dst->st_ino = (__mpu_uint64_t)src->st_ino; + if( (ino_t)dst->st_ino != src->st_ino ) return( __MCPU_EOVERFLOW ); + + error = __cpu_stat_mode( src->st_mode, &dst->st_mode ); + if( error ) return( error ); + + dst->st_nlink = (__mpu_uint64_t)src->st_nlink; + if( (nlink_t)dst->st_nlink != src->st_nlink ) return( __MCPU_EOVERFLOW ); + + dst->st_uid = (__mpu_uint64_t)src->st_uid; + if( (uid_t)dst->st_uid != src->st_uid ) return( __MCPU_EOVERFLOW ); + + dst->st_gid = (__mpu_uint64_t)src->st_gid; + if( (gid_t)dst->st_gid != src->st_gid ) return( __MCPU_EOVERFLOW ); + + dst->st_rdev = (__mpu_uint64_t)src->st_rdev; + if( (dev_t)dst->st_rdev != src->st_rdev ) return( __MCPU_EOVERFLOW ); + + dst->st_size = (__mpu_int64_t)src->st_size; + if( (off_t)dst->st_size != src->st_size ) return( __MCPU_EOVERFLOW ); + + dst->st_blksize = (__mpu_int64_t)src->st_blksize; + if( (blksize_t)dst->st_blksize != src->st_blksize ) return( __MCPU_EOVERFLOW ); + + dst->st_blocks = (__mpu_int64_t)src->st_blocks; + if( (blkcnt_t)dst->st_blocks != src->st_blocks ) return( __MCPU_EOVERFLOW ); + + dst->st_atime_sec = (__mpu_int64_t)src->st_atim.tv_sec; + if( (time_t)dst->st_atime_sec != src->st_atim.tv_sec ) return( __MCPU_EOVERFLOW ); + if( src->st_atim.tv_nsec < 0 ) return( __MCPU_EOVERFLOW ); + dst->st_atime_nsec = (__mpu_uint64_t)src->st_atim.tv_nsec; + + dst->st_mtime_sec = (__mpu_int64_t)src->st_mtim.tv_sec; + if( (time_t)dst->st_mtime_sec != src->st_mtim.tv_sec ) return( __MCPU_EOVERFLOW ); + if( src->st_mtim.tv_nsec < 0 ) return( __MCPU_EOVERFLOW ); + dst->st_mtime_nsec = (__mpu_uint64_t)src->st_mtim.tv_nsec; + + dst->st_ctime_sec = (__mpu_int64_t)src->st_ctim.tv_sec; + if( (time_t)dst->st_ctime_sec != src->st_ctim.tv_sec ) return( __MCPU_EOVERFLOW ); + if( src->st_ctim.tv_nsec < 0 ) return( __MCPU_EOVERFLOW ); + dst->st_ctime_nsec = (__mpu_uint64_t)src->st_ctim.tv_nsec; + + return( 0 ); + +} /* End of __cpu_stat_from_host() */ + +#endif /* __CPU_STAT_LINUX_H */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-time-linux.h b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-time-linux.h new file mode 100644 index 0000000..8bdda84 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/cpu-time-linux.h @@ -0,0 +1,64 @@ +/*************************************************************** + CPU-TIME-LINUX.H + + Linux/x86_64/LP64 conversion helpers for the MCPU time ABI. + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __CPU_TIME_LINUX_H +#define __CPU_TIME_LINUX_H + +#include <errno.h> +#include <time.h> + + +static int +__cpu_clockid_to_host( __mpu_uint64_t clockid, clockid_t *host_clockid ) +{ + if( !host_clockid ) return( __MCPU_EFAULT ); + + switch( clockid ) + { + case __CPU_CLOCK_REALTIME: + *host_clockid = CLOCK_REALTIME; + return( 0 ); + case __CPU_CLOCK_MONOTONIC: + *host_clockid = CLOCK_MONOTONIC; + return( 0 ); + default: + return( __MCPU_EINVAL ); + } + +} /* End of __cpu_clockid_to_host() */ + + +static int +__cpu_timespec_from_host( struct __cpu_timespec *dst, + const struct timespec *src ) +{ + __mpu_int64_t sec; + __mpu_int64_t nsec; + + if( !dst || !src ) return( __MCPU_EFAULT ); + + sec = (__mpu_int64_t)src->tv_sec; + if( (time_t)sec != src->tv_sec ) return( __MCPU_EOVERFLOW ); + + nsec = (__mpu_int64_t)src->tv_nsec; + if( (long)nsec != src->tv_nsec ) return( __MCPU_EOVERFLOW ); + if( nsec < 0 || nsec > 999999999 ) return( __MCPU_EOVERFLOW ); + + dst->tv_sec = sec; + dst->tv_nsec = nsec; + + return( 0 ); + +} /* End of __cpu_timespec_from_host() */ + +#endif /* __CPU_TIME_LINUX_H */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-access.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-access.c new file mode 100644 index 0000000..5bb9a45 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-access.c @@ -0,0 +1,42 @@ +/*************************************************************** + SYS-ACCESS.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_access(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_access( int dirfd, const char *path, __mpu_uint64_t mode ) +{ + int host_mode = 0; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + if( mode & ~(__mpu_uint64_t)__CPU_ACCESS_SUPPORTED ) + return( -(__mpu_int64_t)__MCPU_EINVAL ); + + if( mode & __CPU_R_OK ) host_mode |= R_OK; + if( mode & __CPU_W_OK ) host_mode |= W_OK; + if( mode & __CPU_X_OK ) host_mode |= X_OK; + + if( faccessat( dirfd, path, host_mode, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_access() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chdir.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chdir.c new file mode 100644 index 0000000..c6da354 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chdir.c @@ -0,0 +1,53 @@ +/*************************************************************** + SYS-CHDIR.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_cwd_init() and __cpu_sys_chdir(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static __mpu_int64_t +__cpu_open_directory( int dirfd, const char *path ) +{ + int fd; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + fd = openat( dirfd, path, O_PATH | O_DIRECTORY | O_CLOEXEC ); + if( fd < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)fd ); + +} /* End of __cpu_open_directory() */ + + +__mpu_int64_t +__cpu_sys_cwd_init( void ) +{ + return( __cpu_open_directory( AT_FDCWD, "." ) ); + +} /* End of __cpu_sys_cwd_init() */ + + +__mpu_int64_t +__cpu_sys_chdir( int dirfd, const char *path ) +{ + return( __cpu_open_directory( dirfd, path ) ); + +} /* End of __cpu_sys_chdir() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chmod.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chmod.c new file mode 100644 index 0000000..6719bf7 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chmod.c @@ -0,0 +1,41 @@ +/*************************************************************** + SYS-CHMOD.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_chmod(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-mode-linux.h" + + +__mpu_int64_t +__cpu_sys_chmod( int dirfd, const char *path, __mpu_uint64_t mode ) +{ + mode_t host_mode; + int error; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_mode_to_host( mode, &host_mode ); + if( error ) return( -(__mpu_int64_t)error ); + + if( fchmodat( dirfd, path, host_mode, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_chmod() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chown.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chown.c new file mode 100644 index 0000000..42e748e --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-chown.c @@ -0,0 +1,45 @@ +/*************************************************************** + SYS-CHOWN.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_chown(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-chown-linux.h" + + +__mpu_int64_t +__cpu_sys_chown( int dirfd, const char *path, __mpu_uint64_t uid, + __mpu_uint64_t gid ) +{ + uid_t host_uid; + gid_t host_gid; + int error; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_uid_to_host( uid, &host_uid ); + if( error ) return( -(__mpu_int64_t)error ); + error = __cpu_gid_to_host( gid, &host_gid ); + if( error ) return( -(__mpu_int64_t)error ); + + if( fchownat( dirfd, path, host_uid, host_gid, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_chown() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_getres.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_getres.c new file mode 100644 index 0000000..d156d61 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_getres.c @@ -0,0 +1,48 @@ +/*************************************************************** + SYS-CLOCK_GETRES.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_clock_getres(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <time.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-time-linux.h" + + +__mpu_int64_t +__cpu_sys_clock_getres( __mpu_uint64_t clockid, struct __cpu_timespec *res ) +{ + struct timespec ts; + clockid_t host_clockid; + int error; + + error = __cpu_clockid_to_host( clockid, &host_clockid ); + if( error ) return( -(__mpu_int64_t)error ); + + if( clock_getres( host_clockid, res ? &ts : (struct timespec *)0 ) < 0 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + if( res ) + { + error = __cpu_timespec_from_host( res, &ts ); + if( error ) return( -(__mpu_int64_t)error ); + } + + return( 0 ); + +} /* End of __cpu_sys_clock_getres() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_gettime.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_gettime.c new file mode 100644 index 0000000..6e017fc --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_gettime.c @@ -0,0 +1,47 @@ +/*************************************************************** + SYS-CLOCK_GETTIME.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_clock_gettime(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <time.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-time-linux.h" + + +__mpu_int64_t +__cpu_sys_clock_gettime( __mpu_uint64_t clockid, struct __cpu_timespec *tp ) +{ + struct timespec ts; + clockid_t host_clockid; + int error; + + if( !tp ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_clockid_to_host( clockid, &host_clockid ); + if( error ) return( -(__mpu_int64_t)error ); + + if( clock_gettime( host_clockid, &ts ) < 0 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + error = __cpu_timespec_from_host( tp, &ts ); + if( error ) return( -(__mpu_int64_t)error ); + + return( 0 ); + +} /* End of __cpu_sys_clock_gettime() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_nanosleep.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_nanosleep.c new file mode 100644 index 0000000..8659441 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-clock_nanosleep.c @@ -0,0 +1,93 @@ +/*************************************************************** + SYS-CLOCK_NANOSLEEP.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_clock_nanosleep(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <time.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-time-linux.h" + + +static int +__cpu_timespec_to_host( struct timespec *dst, + const struct __cpu_timespec *src ) +{ + time_t sec; + long nsec; + + if( !dst || !src ) return( __MCPU_EFAULT ); + if( src->tv_sec < 0 || src->tv_nsec < 0 || src->tv_nsec > 999999999 ) + return( __MCPU_EINVAL ); + + sec = (time_t)src->tv_sec; + if( (__mpu_int64_t)sec != src->tv_sec ) return( __MCPU_EOVERFLOW ); + + nsec = (long)src->tv_nsec; + if( (__mpu_int64_t)nsec != src->tv_nsec ) return( __MCPU_EOVERFLOW ); + + dst->tv_sec = sec; + dst->tv_nsec = nsec; + + return( 0 ); + +} /* End of __cpu_timespec_to_host() */ + + +__mpu_int64_t +__cpu_sys_clock_nanosleep( __mpu_uint64_t clockid, __mpu_uint64_t flags, + const struct __cpu_timespec *req, + struct __cpu_timespec *rem ) +{ + struct timespec host_req; + struct timespec host_rem; + struct timespec *host_prem = (struct timespec *)0; + clockid_t host_clockid; + int host_flags = 0; + int ret; + int error; + + if( !req ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_clockid_to_host( clockid, &host_clockid ); + if( error ) return( -(__mpu_int64_t)error ); + + if( flags == __CPU_TIMER_ABSTIME ) + host_flags = TIMER_ABSTIME; + else if( flags != 0 ) + return( -(__mpu_int64_t)__MCPU_EINVAL ); + + error = __cpu_timespec_to_host( &host_req, req ); + if( error ) return( -(__mpu_int64_t)error ); + + if( !host_flags && rem ) host_prem = &host_rem; + + ret = clock_nanosleep( host_clockid, host_flags, &host_req, host_prem ); + if( ret != 0 ) + { + if( ret == EINTR && host_prem ) + { + error = __cpu_timespec_from_host( rem, &host_rem ); + if( error ) return( -(__mpu_int64_t)error ); + } + return( -(__mpu_int64_t)__cpu_errno_from_host( ret ) ); + } + + return( 0 ); + +} /* End of __cpu_sys_clock_nanosleep() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-close.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-close.c new file mode 100644 index 0000000..48aceed --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-close.c @@ -0,0 +1,16 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + +__mpu_int64_t +__cpu_sys_close( int fd ) +{ + if( close( fd ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); +} diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-creat.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-creat.c new file mode 100644 index 0000000..9bbccce --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-creat.c @@ -0,0 +1,30 @@ +/*************************************************************** + SYS-CREAT.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_creat(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_creat( int dirfd, const char *path, __mpu_uint64_t mode ) +{ + return( __cpu_sys_open( dirfd, path, + __CPU_O_WRONLY | __CPU_O_CREAT | __CPU_O_TRUNC, + mode ) ); + +} /* End of __cpu_sys_creat() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-dup.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-dup.c new file mode 100644 index 0000000..f140b69 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-dup.c @@ -0,0 +1,36 @@ +/*************************************************************** + SYS-DUP.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_dup(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_dup( int fd ) +{ + int ret; + + ret = dup( fd ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + return( (__mpu_int64_t)ret ); + +} /* End of __cpu_sys_dup() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchdir.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchdir.c new file mode 100644 index 0000000..3323975 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchdir.c @@ -0,0 +1,35 @@ +/*************************************************************** + SYS-FCHDIR.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fchdir(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_fchdir( int fd ) +{ + int cwd_fd; + + cwd_fd = openat( fd, ".", O_PATH | O_DIRECTORY | O_CLOEXEC ); + if( cwd_fd < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)cwd_fd ); + +} /* End of __cpu_sys_fchdir() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchmod.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchmod.c new file mode 100644 index 0000000..a4f90be --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchmod.c @@ -0,0 +1,39 @@ +/*************************************************************** + SYS-FCHMOD.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fchmod(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-mode-linux.h" + + +__mpu_int64_t +__cpu_sys_fchmod( int fd, __mpu_uint64_t mode ) +{ + mode_t host_mode; + int error; + + error = __cpu_mode_to_host( mode, &host_mode ); + if( error ) return( -(__mpu_int64_t)error ); + + if( fchmod( fd, host_mode ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_fchmod() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchown.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchown.c new file mode 100644 index 0000000..a3e793b --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fchown.c @@ -0,0 +1,42 @@ +/*************************************************************** + SYS-FCHOWN.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fchown(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-chown-linux.h" + + +__mpu_int64_t +__cpu_sys_fchown( int fd, __mpu_uint64_t uid, __mpu_uint64_t gid ) +{ + uid_t host_uid; + gid_t host_gid; + int error; + + error = __cpu_uid_to_host( uid, &host_uid ); + if( error ) return( -(__mpu_int64_t)error ); + error = __cpu_gid_to_host( gid, &host_gid ); + if( error ) return( -(__mpu_int64_t)error ); + + if( fchown( fd, host_uid, host_gid ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_fchown() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fcntl.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fcntl.c new file mode 100644 index 0000000..d04074e --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fcntl.c @@ -0,0 +1,115 @@ +/*************************************************************** + SYS-FCNTL.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fcntl(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_fcntl_getfl( int host_flags, __mpu_uint64_t *guest_flags ) +{ + __mpu_uint64_t flags = 0; + + if( !guest_flags ) return( __MCPU_EINVAL ); + + switch( host_flags & O_ACCMODE ) + { + case O_RDONLY: flags |= __CPU_O_RDONLY; break; + case O_WRONLY: flags |= __CPU_O_WRONLY; break; + case O_RDWR: flags |= __CPU_O_RDWR; break; + default: return( __MCPU_EINVAL ); + } + + if( host_flags & O_APPEND ) flags |= __CPU_O_APPEND; + if( host_flags & O_NONBLOCK ) flags |= __CPU_O_NONBLOCK; + + *guest_flags = flags; + return( 0 ); + +} /* End of __cpu_fcntl_getfl() */ + + +static int +__cpu_fcntl_setfl( __mpu_uint64_t guest_flags, int *host_flags ) +{ + int flags = 0; + + if( !host_flags ) return( __MCPU_EINVAL ); + if( guest_flags & ~(__mpu_uint64_t)__CPU_F_SETFL_SUPPORTED ) + return( __MCPU_EINVAL ); + + switch( guest_flags & __CPU_O_ACCMODE ) + { + case __CPU_O_RDONLY: + case __CPU_O_WRONLY: + case __CPU_O_RDWR: + break; + default: + return( __MCPU_EINVAL ); + } + + if( guest_flags & __CPU_O_APPEND ) flags |= O_APPEND; + if( guest_flags & __CPU_O_NONBLOCK ) flags |= O_NONBLOCK; + + *host_flags = flags; + return( 0 ); + +} /* End of __cpu_fcntl_setfl() */ + + +__mpu_int64_t +__cpu_sys_fcntl( int fd, __mpu_uint64_t cmd, __mpu_uint64_t arg ) +{ + __mpu_uint64_t guest_flags; + int host_flags; + int error; + int ret; + + switch( cmd ) + { + case __CPU_F_DUPFD: + if( arg != 0 ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + ret = fcntl( fd, F_DUPFD, 0 ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)ret ); + + case __CPU_F_GETFL: + if( arg != 0 ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + ret = fcntl( fd, F_GETFL ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + error = __cpu_fcntl_getfl( ret, &guest_flags ); + if( error ) return( -(__mpu_int64_t)error ); + return( (__mpu_int64_t)guest_flags ); + + case __CPU_F_SETFL: + error = __cpu_fcntl_setfl( arg, &host_flags ); + if( error ) return( -(__mpu_int64_t)error ); + + ret = fcntl( fd, F_SETFL, host_flags ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + + default: + return( -(__mpu_int64_t)__MCPU_EINVAL ); + } + +} /* End of __cpu_sys_fcntl() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fstat.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fstat.c new file mode 100644 index 0000000..53b0309 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fstat.c @@ -0,0 +1,43 @@ +/*************************************************************** + SYS-FSTAT.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fstat(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-stat-linux.h" + + +__mpu_int64_t +__cpu_sys_fstat( int fd, struct __cpu_stat *st ) +{ + struct stat host_st; + int error; + + if( fd < 0 ) return( -(__mpu_int64_t)__MCPU_EBADF ); + if( !st ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( fstat( fd, &host_st ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + error = __cpu_stat_from_host( st, &host_st ); + if( error ) return( -(__mpu_int64_t)error ); + + return( 0 ); + +} /* End of __cpu_sys_fstat() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fsync.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fsync.c new file mode 100644 index 0000000..3ba7e11 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-fsync.c @@ -0,0 +1,32 @@ +/*************************************************************** + SYS-FSYNC.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_fsync(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_fsync( int fd ) +{ + if( fsync( fd ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_fsync() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-ftruncate.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-ftruncate.c new file mode 100644 index 0000000..f5e55ad --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-ftruncate.c @@ -0,0 +1,39 @@ +/*************************************************************** + SYS-FTRUNCATE.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_ftruncate(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_ftruncate( int fd, __mpu_int64_t length ) +{ + off_t host_length; + + host_length = (off_t)length; + if( (__mpu_int64_t)host_length != length ) + return( -(__mpu_int64_t)__MCPU_EOVERFLOW ); + + if( ftruncate( fd, host_length ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_ftruncate() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-getdents.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-getdents.c new file mode 100644 index 0000000..6b8928c --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-getdents.c @@ -0,0 +1,227 @@ +/*************************************************************** + SYS-GETDENTS.C + + Linux/x86_64/LP64 implementation of __cpu_sys_getdents(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dirent.h> +#include <errno.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + +#define __CPU_HOST_DIRENT_BUFFER 4096U + +struct __cpu_linux_dirent64 +{ + uint64_t d_ino; + int64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +static int +__cpu_dirent_utf8_valid( const unsigned char *s, size_t n ) +{ + size_t i = 0; + + while( i < n ) + { + unsigned int c = s[i++]; + + if( c < 0x80U ) continue; + if( c >= 0xc2U && c <= 0xdfU ) + { + if( i >= n || ( s[i] & 0xc0U ) != 0x80U ) return( 0 ); + ++i; continue; + } + if( c == 0xe0U ) + { + if( i + 1 >= n || s[i] < 0xa0U || s[i] > 0xbfU || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; continue; + } + if( c >= 0xe1U && c <= 0xecU ) + { + if( i + 1 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; continue; + } + if( c == 0xedU ) + { + if( i + 1 >= n || s[i] < 0x80U || s[i] > 0x9fU || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; continue; + } + if( c >= 0xeeU && c <= 0xefU ) + { + if( i + 1 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U ) return( 0 ); + i += 2; continue; + } + if( c == 0xf0U ) + { + if( i + 2 >= n || s[i] < 0x90U || s[i] > 0xbfU || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; continue; + } + if( c >= 0xf1U && c <= 0xf3U ) + { + if( i + 2 >= n || ( s[i] & 0xc0U ) != 0x80U || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; continue; + } + if( c == 0xf4U ) + { + if( i + 2 >= n || s[i] < 0x80U || s[i] > 0x8fU || + ( s[i + 1] & 0xc0U ) != 0x80U || + ( s[i + 2] & 0xc0U ) != 0x80U ) return( 0 ); + i += 3; continue; + } + return( 0 ); + } + return( 1 ); +} + +static __mpu_uint64_t +__cpu_dirent_type( unsigned char type ) +{ + switch( type ) + { +#ifdef DT_FIFO + case DT_FIFO: return( __CPU_DT_FIFO ); +#endif +#ifdef DT_CHR + case DT_CHR: return( __CPU_DT_CHR ); +#endif +#ifdef DT_DIR + case DT_DIR: return( __CPU_DT_DIR ); +#endif +#ifdef DT_BLK + case DT_BLK: return( __CPU_DT_BLK ); +#endif +#ifdef DT_REG + case DT_REG: return( __CPU_DT_REG ); +#endif +#ifdef DT_LNK + case DT_LNK: return( __CPU_DT_LNK ); +#endif +#ifdef DT_SOCK + case DT_SOCK: return( __CPU_DT_SOCK ); +#endif + default: return( __CPU_DT_UNKNOWN ); + } +} + +__mpu_int64_t +__cpu_sys_getdents( int fd, struct __cpu_dirent *dirp, __mpu_uint64_t count ) +{ + unsigned char hostbuf[__CPU_HOST_DIRENT_BUFFER]; + struct stat st; + off_t start; + off_t resume; + ssize_t nread; + size_t pos = 0; + size_t nrecords = 0; + size_t maxrecords; + + if( !dirp ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + if( count < __CPU_DIRENT_SIZE ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + + maxrecords = (size_t)( count / __CPU_DIRENT_SIZE ); + + if( fstat( fd, &st ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + if( !S_ISDIR( st.st_mode ) ) return( -(__mpu_int64_t)__MCPU_ENOTDIR ); + + start = lseek( fd, (off_t)0, SEEK_CUR ); + if( start == (off_t)-1 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + resume = start; + + nread = syscall( SYS_getdents64, fd, hostbuf, sizeof( hostbuf ) ); + if( nread < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + if( nread == 0 ) return( 0 ); + + while( pos < (size_t)nread ) + { + struct __cpu_linux_dirent64 *de; + const char *nul; + size_t namespace; + size_t namelen; + + if( (size_t)nread - pos < offsetof( struct __cpu_linux_dirent64, d_name ) + 1U ) + goto malformed; + + de = (struct __cpu_linux_dirent64 *)( hostbuf + pos ); + if( de->d_reclen < offsetof( struct __cpu_linux_dirent64, d_name ) + 1U || + de->d_reclen > (size_t)nread - pos ) goto malformed; + + if( nrecords >= maxrecords ) break; + + namespace = de->d_reclen - offsetof( struct __cpu_linux_dirent64, d_name ); + nul = (const char *)memchr( de->d_name, '\0', namespace ); + if( !nul ) goto malformed; + namelen = (size_t)( nul - de->d_name ); + + if( namelen >= __CPU_DIRENT_NAME_SIZE ) + { + if( lseek( fd, resume, SEEK_SET ) == (off_t)-1 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( nrecords ? (__mpu_int64_t)( nrecords * __CPU_DIRENT_SIZE ) + : -(__mpu_int64_t)__MCPU_ENAMETOOLONG ); + } + + if( !__cpu_dirent_utf8_valid( (const unsigned char *)de->d_name, namelen ) ) + { + if( lseek( fd, resume, SEEK_SET ) == (off_t)-1 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( nrecords ? (__mpu_int64_t)( nrecords * __CPU_DIRENT_SIZE ) + : -(__mpu_int64_t)__MCPU_EILSEQ ); + } + + dirp[nrecords].d_ino = (__mpu_uint64_t)de->d_ino; + dirp[nrecords].d_off = (__mpu_int64_t)de->d_off; + dirp[nrecords].d_reclen = __CPU_DIRENT_SIZE; + dirp[nrecords].d_type = __cpu_dirent_type( de->d_type ); + memset( dirp[nrecords].d_name, 0, __CPU_DIRENT_NAME_SIZE ); + memcpy( dirp[nrecords].d_name, de->d_name, namelen ); + + resume = (off_t)de->d_off; + ++nrecords; + pos += de->d_reclen; + } + + if( pos < (size_t)nread ) + { + if( lseek( fd, resume, SEEK_SET ) == (off_t)-1 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + } + + return( (__mpu_int64_t)( nrecords * __CPU_DIRENT_SIZE ) ); + +malformed: + if( lseek( fd, start, SEEK_SET ) == (off_t)-1 ) + return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( -(__mpu_int64_t)__MCPU_EIO ); + +} /* End of __cpu_sys_getdents() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-link.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-link.c new file mode 100644 index 0000000..fd19f0d --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-link.c @@ -0,0 +1,34 @@ +/*************************************************************** + SYS-LINK.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_link(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_link( int dirfd, const char *oldpath, const char *newpath ) +{ + if( !oldpath || !newpath ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( linkat( dirfd, oldpath, dirfd, newpath, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_link() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lseek.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lseek.c new file mode 100644 index 0000000..0de650e --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lseek.c @@ -0,0 +1,35 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + +__mpu_int64_t +__cpu_sys_lseek( int fd, __mpu_int64_t offset, __mpu_uint64_t whence ) +{ + off_t host_offset; + off_t ret; + int host_whence; + + switch( whence ) + { + case __CPU_SEEK_SET: host_whence = SEEK_SET; break; + case __CPU_SEEK_CUR: host_whence = SEEK_CUR; break; + case __CPU_SEEK_END: host_whence = SEEK_END; break; + default: return( -(__mpu_int64_t)__MCPU_EINVAL ); + } + + host_offset = (off_t)offset; + if( (__mpu_int64_t)host_offset != offset ) return( -(__mpu_int64_t)__MCPU_EOVERFLOW ); + + ret = lseek( fd, host_offset, host_whence ); + if( ret == (off_t)-1 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + if( (off_t)(__mpu_int64_t)ret != ret ) return( -(__mpu_int64_t)__MCPU_EOVERFLOW ); + return( (__mpu_int64_t)ret ); +} diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lstat.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lstat.c new file mode 100644 index 0000000..3ea9353 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-lstat.c @@ -0,0 +1,43 @@ +/*************************************************************** + SYS-LSTAT.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_lstat(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-stat-linux.h" + + +__mpu_int64_t +__cpu_sys_lstat( int dirfd, const char *path, struct __cpu_stat *st ) +{ + struct stat host_st; + int error; + + if( !path || !st ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( fstatat( dirfd, path, &host_st, AT_SYMLINK_NOFOLLOW ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + error = __cpu_stat_from_host( st, &host_st ); + if( error ) return( -(__mpu_int64_t)error ); + + return( 0 ); + +} /* End of __cpu_sys_lstat() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-mkdir.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-mkdir.c new file mode 100644 index 0000000..76ced3a --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-mkdir.c @@ -0,0 +1,69 @@ +/*************************************************************** + SYS-MKDIR.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_mkdir(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_mkdir_mode( __mpu_uint64_t mode, mode_t *host_mode ) +{ + mode_t m = 0; + + if( !host_mode ) return( __MCPU_EINVAL ); + if( mode & ~(__mpu_uint64_t)__CPU_S_SUPPORTED ) return( __MCPU_EINVAL ); + + if( mode & __CPU_S_ISUID ) m |= S_ISUID; + if( mode & __CPU_S_ISGID ) m |= S_ISGID; +#ifdef S_ISVTX + if( mode & __CPU_S_ISVTX ) m |= S_ISVTX; +#endif + if( mode & __CPU_S_IRUSR ) m |= S_IRUSR; + if( mode & __CPU_S_IWUSR ) m |= S_IWUSR; + if( mode & __CPU_S_IXUSR ) m |= S_IXUSR; + if( mode & __CPU_S_IRGRP ) m |= S_IRGRP; + if( mode & __CPU_S_IWGRP ) m |= S_IWGRP; + if( mode & __CPU_S_IXGRP ) m |= S_IXGRP; + if( mode & __CPU_S_IROTH ) m |= S_IROTH; + if( mode & __CPU_S_IWOTH ) m |= S_IWOTH; + if( mode & __CPU_S_IXOTH ) m |= S_IXOTH; + + *host_mode = m; + return( 0 ); + +} /* End of __cpu_mkdir_mode() */ + + +__mpu_int64_t +__cpu_sys_mkdir( int dirfd, const char *path, __mpu_uint64_t mode ) +{ + mode_t host_mode; + int error; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_mkdir_mode( mode, &host_mode ); + if( error ) return( -(__mpu_int64_t)error ); + + if( mkdirat( dirfd, path, host_mode ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_mkdir() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-open.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-open.c new file mode 100644 index 0000000..36a9994 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-open.c @@ -0,0 +1,114 @@ +/*************************************************************** + SYS-OPEN.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_open(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +static int +__cpu_open_flags( __mpu_uint64_t flags, int *host_flags ) +{ + int f = 0; + + if( !host_flags ) return( __MCPU_EINVAL ); + if( flags & ~(__mpu_uint64_t)__CPU_O_SUPPORTED ) return( __MCPU_EINVAL ); + + switch( flags & __CPU_O_ACCMODE ) + { + case __CPU_O_RDONLY: f |= O_RDONLY; break; + case __CPU_O_WRONLY: f |= O_WRONLY; break; + case __CPU_O_RDWR: f |= O_RDWR; break; + default: return( __MCPU_EINVAL ); + } + + if( flags & __CPU_O_CREAT ) f |= O_CREAT; + if( flags & __CPU_O_EXCL ) f |= O_EXCL; + if( flags & __CPU_O_NOCTTY ) f |= O_NOCTTY; + if( flags & __CPU_O_TRUNC ) f |= O_TRUNC; + if( flags & __CPU_O_APPEND ) f |= O_APPEND; + if( flags & __CPU_O_NONBLOCK ) f |= O_NONBLOCK; + + *host_flags = f; + return( 0 ); + +} /* End of __cpu_open_flags() */ + + +static int +__cpu_open_mode( __mpu_uint64_t mode, mode_t *host_mode ) +{ + mode_t m = 0; + + if( !host_mode ) return( __MCPU_EINVAL ); + if( mode & ~(__mpu_uint64_t)__CPU_S_SUPPORTED ) return( __MCPU_EINVAL ); + + if( mode & __CPU_S_ISUID ) m |= S_ISUID; + if( mode & __CPU_S_ISGID ) m |= S_ISGID; +#ifdef S_ISVTX + if( mode & __CPU_S_ISVTX ) m |= S_ISVTX; +#endif + if( mode & __CPU_S_IRUSR ) m |= S_IRUSR; + if( mode & __CPU_S_IWUSR ) m |= S_IWUSR; + if( mode & __CPU_S_IXUSR ) m |= S_IXUSR; + if( mode & __CPU_S_IRGRP ) m |= S_IRGRP; + if( mode & __CPU_S_IWGRP ) m |= S_IWGRP; + if( mode & __CPU_S_IXGRP ) m |= S_IXGRP; + if( mode & __CPU_S_IROTH ) m |= S_IROTH; + if( mode & __CPU_S_IWOTH ) m |= S_IWOTH; + if( mode & __CPU_S_IXOTH ) m |= S_IXOTH; + + *host_mode = m; + return( 0 ); + +} /* End of __cpu_open_mode() */ + + +__mpu_int64_t +__cpu_sys_open( int dirfd, const char *path, __mpu_uint64_t flags, + __mpu_uint64_t mode ) +{ + mode_t host_mode = 0; + int host_flags; + int error; + int fd; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + error = __cpu_open_flags( flags, &host_flags ); + if( error ) return( -(__mpu_int64_t)error ); + + if( flags & __CPU_O_CREAT ) + { + error = __cpu_open_mode( mode, &host_mode ); + if( error ) return( -(__mpu_int64_t)error ); + fd = openat( dirfd, path, host_flags, host_mode ); + } + else + { + if( mode != 0 ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + fd = openat( dirfd, path, host_flags ); + } + + if( fd < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)fd ); + +} /* End of __cpu_sys_open() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-pipe2.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-pipe2.c new file mode 100644 index 0000000..bb211a2 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-pipe2.c @@ -0,0 +1,44 @@ +/*************************************************************** + SYS-PIPE2.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_pipe2(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_pipe2( int pipefd[2], __mpu_uint64_t flags ) +{ + int host_flags = 0; + int ret; + + if( !pipefd ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + if( flags & ~(__mpu_uint64_t)__CPU_O_NONBLOCK ) + return( -(__mpu_int64_t)__MCPU_EINVAL ); + + if( flags & __CPU_O_NONBLOCK ) host_flags |= O_NONBLOCK; + + ret = pipe2( pipefd, host_flags ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + return( 0 ); + +} /* End of __cpu_sys_pipe2() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-read.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-read.c new file mode 100644 index 0000000..4816d37 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-read.c @@ -0,0 +1,21 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + +__mpu_int64_t +__cpu_sys_read( int fd, void *buf, __mpu_uint64_t count ) +{ + ssize_t ret; + + if( count > (__mpu_uint64_t)SIZE_MAX ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + ret = read( fd, buf, (size_t)count ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)ret ); +} diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-readlink.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-readlink.c new file mode 100644 index 0000000..bda2a47 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-readlink.c @@ -0,0 +1,40 @@ +/*************************************************************** + SYS-READLINK.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_readlink(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_readlink( int dirfd, const char *path, void *buf, + __mpu_uint64_t count ) +{ + ssize_t ret; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + if( count > (__mpu_uint64_t)SIZE_MAX ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + + ret = readlinkat( dirfd, path, (char *)buf, (size_t)count ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)ret ); + +} /* End of __cpu_sys_readlink() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rename.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rename.c new file mode 100644 index 0000000..d79b3fb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rename.c @@ -0,0 +1,34 @@ +/*************************************************************** + SYS-RENAME.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_rename(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_rename( int dirfd, const char *oldpath, const char *newpath ) +{ + if( !oldpath || !newpath ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( renameat( dirfd, oldpath, dirfd, newpath ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_rename() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rmdir.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rmdir.c new file mode 100644 index 0000000..41ee0c2 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-rmdir.c @@ -0,0 +1,35 @@ +/*************************************************************** + SYS-RMDIR.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_rmdir(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_rmdir( int dirfd, const char *path ) +{ + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( unlinkat( dirfd, path, AT_REMOVEDIR ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_rmdir() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-select.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-select.c new file mode 100644 index 0000000..a87d138 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-select.c @@ -0,0 +1,109 @@ +/*************************************************************** + SYS-SELECT.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_select(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_select( struct __cpu_select_entry *entries, __mpu_uint64_t count, + const struct __cpu_timeval *timeout ) +{ + struct pollfd *pfds = (struct pollfd *)0; + struct timespec ts; + struct timespec *pts = (struct timespec *)0; + __mpu_uint64_t i; + time_t sec; + int ret; + + if( count && !entries ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + if( count > (__mpu_uint64_t)(size_t)-1 / sizeof( *pfds ) ) + return( -(__mpu_int64_t)__MCPU_EINVAL ); + + if( timeout ) + { + if( timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec > 999999 ) + return( -(__mpu_int64_t)__MCPU_EINVAL ); + + sec = (time_t)timeout->tv_sec; + if( (__mpu_int64_t)sec != timeout->tv_sec ) + return( -(__mpu_int64_t)__MCPU_EOVERFLOW ); + + ts.tv_sec = sec; + ts.tv_nsec = (long)( timeout->tv_usec * 1000 ); + pts = &ts; + } + + if( count ) + { + pfds = (struct pollfd *)calloc( (size_t)count, sizeof( *pfds ) ); + if( !pfds ) return( -(__mpu_int64_t)__MCPU_ENOMEM ); + } + + for( i = 0; i < count; ++i ) + { + pfds[i].fd = entries[i].host_fd; + pfds[i].events = 0; + pfds[i].revents = 0; + entries[i].revents = 0; + + if( entries[i].events & __CPU_SELECT_READ ) pfds[i].events |= POLLIN; + if( entries[i].events & __CPU_SELECT_WRITE ) pfds[i].events |= POLLOUT; + if( entries[i].events & __CPU_SELECT_EXCEPT ) pfds[i].events |= POLLPRI; + } + + ret = ppoll( pfds, (nfds_t)count, pts, (const sigset_t *)0 ); + if( ret < 0 ) + { + ret = -(__mpu_int64_t)__cpu_errno_from_host( errno ); + free( pfds ); + return( (__mpu_int64_t)ret ); + } + + for( i = 0; i < count; ++i ) + { + short revents = pfds[i].revents; + + if( revents & POLLNVAL ) + { + free( pfds ); + return( -(__mpu_int64_t)__MCPU_EBADF ); + } + + if( ( entries[i].events & __CPU_SELECT_READ ) && + ( revents & ( POLLIN | POLLHUP | POLLERR ) ) ) + entries[i].revents |= __CPU_SELECT_READ; + + if( ( entries[i].events & __CPU_SELECT_WRITE ) && + ( revents & ( POLLOUT | POLLERR ) ) ) + entries[i].revents |= __CPU_SELECT_WRITE; + + if( ( entries[i].events & __CPU_SELECT_EXCEPT ) && ( revents & POLLPRI ) ) + entries[i].revents |= __CPU_SELECT_EXCEPT; + } + + free( pfds ); + return( 0 ); + +} /* End of __cpu_sys_select() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-stat.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-stat.c new file mode 100644 index 0000000..bb3a154 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-stat.c @@ -0,0 +1,42 @@ +/*************************************************************** + SYS-STAT.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_stat(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/stat.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> +#include "cpu-stat-linux.h" + + +__mpu_int64_t +__cpu_sys_stat( int dirfd, const char *path, struct __cpu_stat *st ) +{ + struct stat host_st; + int error; + + if( !path || !st ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( fstatat( dirfd, path, &host_st, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + error = __cpu_stat_from_host( st, &host_st ); + if( error ) return( -(__mpu_int64_t)error ); + + return( 0 ); + +} /* End of __cpu_sys_stat() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-symlink.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-symlink.c new file mode 100644 index 0000000..276a277 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-symlink.c @@ -0,0 +1,34 @@ +/*************************************************************** + SYS-SYMLINK.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_symlink(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_symlink( int dirfd, const char *target, const char *linkpath ) +{ + if( !target || !linkpath ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( symlinkat( target, dirfd, linkpath ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_symlink() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-truncate.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-truncate.c new file mode 100644 index 0000000..4a34b44 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-truncate.c @@ -0,0 +1,58 @@ +/*************************************************************** + SYS-TRUNCATE.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_truncate(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_truncate( int dirfd, const char *path, __mpu_int64_t length ) +{ + off_t host_length; + + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + host_length = (off_t)length; + if( (__mpu_int64_t)host_length != length ) + return( -(__mpu_int64_t)__MCPU_EOVERFLOW ); + + { + int fd; + int error; + + fd = openat( dirfd, path, O_WRONLY | O_CLOEXEC ); + if( fd < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + + if( ftruncate( fd, host_length ) < 0 ) + { + error = errno; + (void)close( fd ); + return( -(__mpu_int64_t)__cpu_errno_from_host( error ) ); + } + + (void)close( fd ); + } + + return( 0 ); + +} /* End of __cpu_sys_truncate() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-unlink.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-unlink.c new file mode 100644 index 0000000..155dc79 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-unlink.c @@ -0,0 +1,34 @@ +/*************************************************************** + SYS-UNLINK.C + + This file contains Linux/x86_64/LP64 implementation of + __cpu_sys_unlink(). + + PART OF : CPU - library . + + USAGE : Internal only . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + + +__mpu_int64_t +__cpu_sys_unlink( int dirfd, const char *path ) +{ + if( !path ) return( -(__mpu_int64_t)__MCPU_EFAULT ); + + if( unlinkat( dirfd, path, 0 ) < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( 0 ); + +} /* End of __cpu_sys_unlink() */ diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-write.c b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-write.c new file mode 100644 index 0000000..e5398c7 --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/64/sys-write.c @@ -0,0 +1,21 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <cpu-sysdeps.h> + +__mpu_int64_t +__cpu_sys_write( int fd, const void *buf, __mpu_uint64_t count ) +{ + ssize_t ret; + + if( count > (__mpu_uint64_t)SIZE_MAX ) return( -(__mpu_int64_t)__MCPU_EINVAL ); + ret = write( fd, buf, (size_t)count ); + if( ret < 0 ) return( -(__mpu_int64_t)__cpu_errno_from_host( errno ) ); + return( (__mpu_int64_t)ret ); +} diff --git a/cpu/sysdeps/unix/sysv/linux/x86_64/x32/README b/cpu/sysdeps/unix/sysv/linux/x86_64/x32/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/unix/sysv/linux/x86_64/x32/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/x86_64/64/README b/cpu/sysdeps/x86_64/64/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/x86_64/64/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/cpu/sysdeps/x86_64/configure-fragment b/cpu/sysdeps/x86_64/configure-fragment new file mode 100755 index 0000000..a64629c --- /dev/null +++ b/cpu/sysdeps/x86_64/configure-fragment @@ -0,0 +1,8 @@ +#!/bin/sh + +# Regenerate this directory's sourced preconfigure fragment from preconfigure.ac. +# Run from the architecture sysdeps directory. +autoconf -Wno-syntax -I ../../.. -o preconfigure preconfigure.ac || exit 1 +chmod a-x preconfigure +rm -f preconfigure~ +rm -rf autom4te.cache diff --git a/cpu/sysdeps/x86_64/preconfigure b/cpu/sysdeps/x86_64/preconfigure new file mode 100644 index 0000000..9abbeec --- /dev/null +++ b/cpu/sysdeps/x86_64/preconfigure @@ -0,0 +1,79 @@ +# This file is generated from preconfigure.ac by Autoconf. DO NOT EDIT! + +# Local preconfigure fragment for cpu/sysdeps/x86_64. + +test -n "$base_machine" || case "$machine" in +x86_64) + base_machine=x86_64 + + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC uses the x86_64 x32 data model by default" >&5 +printf %s "checking whether $CC uses the x86_64 x32 data model by default... " >&6; } +if test ${mcpu_cv_x86_64_x32+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#ifndef __ILP32__ +# error not x32 +#endif +int main( void ) { return 0; } + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + mcpu_cv_x86_64_x32=yes +else case e in #( + e) mcpu_cv_x86_64_x32=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $mcpu_cv_x86_64_x32" >&5 +printf "%s\n" "$mcpu_cv_x86_64_x32" >&6; } + + if test "$mcpu_cv_x86_64_x32" = yes; then + machine=x86_64/x32 + MCPU_HOST_DATA_MODEL=ILP32 + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC uses the x86_64 LP64 data model" >&5 +printf %s "checking whether $CC uses the x86_64 LP64 data model... " >&6; } +if test ${mcpu_cv_x86_64_lp64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +typedef char mcpu_int_must_be_4[(sizeof(int) == 4) ? 1 : -1]; +typedef char mcpu_long_must_be_8[(sizeof(long) == 8) ? 1 : -1]; +typedef char mcpu_ptr_must_be_8[(sizeof(void *) == 8) ? 1 : -1]; +int main( void ) { return 0; } + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + mcpu_cv_x86_64_lp64=yes +else case e in #( + e) mcpu_cv_x86_64_lp64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $mcpu_cv_x86_64_lp64" >&5 +printf "%s\n" "$mcpu_cv_x86_64_lp64" >&6; } + if test "$mcpu_cv_x86_64_lp64" != yes; then + as_fn_error $? "Not supported HOST $host: unsupported x86_64 data model" "$LINENO" 5 + fi + machine=x86_64/64 + MCPU_HOST_DATA_MODEL=LP64 + fi + ;; +esac + diff --git a/cpu/sysdeps/x86_64/preconfigure.ac b/cpu/sysdeps/x86_64/preconfigure.ac new file mode 100644 index 0000000..7679c59 --- /dev/null +++ b/cpu/sysdeps/x86_64/preconfigure.ac @@ -0,0 +1,39 @@ +dnl See acsite.m4 in the top-level source directory. +MCPU_PROVIDES +# Local preconfigure fragment for cpu/sysdeps/x86_64. + +test -n "$base_machine" || case "$machine" in +x86_64) + base_machine=x86_64 + + AC_CACHE_CHECK([whether $CC uses the x86_64 x32 data model by default], + [mcpu_cv_x86_64_x32], [dnl + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#ifndef __ILP32__ +# error not x32 +#endif +int main( void ) { return 0; } + ]])], [mcpu_cv_x86_64_x32=yes], [mcpu_cv_x86_64_x32=no]) + ]) + + if test "$mcpu_cv_x86_64_x32" = yes; then + machine=x86_64/x32 + MCPU_HOST_DATA_MODEL=ILP32 + else + AC_CACHE_CHECK([whether $CC uses the x86_64 LP64 data model], + [mcpu_cv_x86_64_lp64], [dnl + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +typedef char mcpu_int_must_be_4[(sizeof(int) == 4) ? 1 : -1]; +typedef char mcpu_long_must_be_8[(sizeof(long) == 8) ? 1 : -1]; +typedef char mcpu_ptr_must_be_8[(sizeof(void *) == 8) ? 1 : -1]; +int main( void ) { return 0; } + ]])], [mcpu_cv_x86_64_lp64=yes], [mcpu_cv_x86_64_lp64=no]) + ]) + if test "$mcpu_cv_x86_64_lp64" != yes; then + AC_MSG_ERROR([Not supported HOST $host: unsupported x86_64 data model]) + fi + machine=x86_64/64 + MCPU_HOST_DATA_MODEL=LP64 + fi + ;; +esac diff --git a/cpu/sysdeps/x86_64/x32/README b/cpu/sysdeps/x86_64/x32/README new file mode 100644 index 0000000..f00b0cb --- /dev/null +++ b/cpu/sysdeps/x86_64/x32/README @@ -0,0 +1,2 @@ +This directory is part of the LibMCPU HOST sysdeps inheritance tree. +HOST-dependent syscall wrappers will be added here in later releases. diff --git a/doc/libmcpu-bytecode-architecture-ru.pdf b/doc/libmcpu-bytecode-architecture-ru.pdf Binary files differnew file mode 100644 index 0000000..25a5425 --- /dev/null +++ b/doc/libmcpu-bytecode-architecture-ru.pdf diff --git a/doc/libmcpu-melf-abi-ru.pdf b/doc/libmcpu-melf-abi-ru.pdf Binary files differnew file mode 100644 index 0000000..334916c --- /dev/null +++ b/doc/libmcpu-melf-abi-ru.pdf diff --git a/doc/src/README b/doc/src/README new file mode 100644 index 0000000..cd2f22b --- /dev/null +++ b/doc/src/README @@ -0,0 +1,28 @@ +LibMCPU documentation sources +============================= + +These are the editable XeLaTeX sources for: + + libmcpu-bytecode-architecture-ru.pdf + libmcpu-melf-abi-ru.pdf + +Build both manuals with: + + ./build-docs.sh + +The manuals intentionally do not embed the LibMCPU package release number. +They document the current architectural contracts; package release history +belongs to NEWS.md and ChangeLog, while installed utilities report the actual +package version through --version / --dump-version. + +The manuals use the memoir-based LibMCPU book style with Roboto body text, +vector TikZ diagrams, booktabs/tabularx tables and breakable command/code +blocks. The build script performs three XeLaTeX passes for each document and +writes both PDFs to doc/. + +The bytecode manual contains the signed HLL Runtime ABI contract joining +mcpu-ld, CRT and MCPU Libc, together with reference appendices for assembler +directives, all architectural opcodes, PSW branch selectors and RCONST +selectors. The MELF manual documents static ABI v1, historical ABI v2 and +the frozen normal dynamic ABI v3, including HLL CRT integration layered above +the MELF container contract. diff --git a/doc/src/build-docs.sh b/doc/src/build-docs.sh new file mode 100755 index 0000000..5078ac3 --- /dev/null +++ b/doc/src/build-docs.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +DIR=$(cd -- "$(dirname -- "$0")" && pwd) +DOC=$(cd -- "$DIR/.." && pwd) +BUILD="$DIR/.build" + +rm -rf "$BUILD" +mkdir -p "$BUILD" +cd "$DIR" + +build_one() +{ + local base="$1" + local pass + for pass in 1 2 3; do + xelatex -interaction=nonstopmode -halt-on-error \ + -output-directory="$BUILD" "$base.tex" >"$BUILD/$base.pass$pass.log" + done + cp -f "$BUILD/$base.pdf" "$DOC/$base.pdf" +} + +build_one libmcpu-bytecode-architecture-ru +build_one libmcpu-melf-abi-ru + +rm -rf "$BUILD" diff --git a/doc/src/libmcpu-architecture-core-ru.tex b/doc/src/libmcpu-architecture-core-ru.tex new file mode 100644 index 0000000..16c6a0f --- /dev/null +++ b/doc/src/libmcpu-architecture-core-ru.tex @@ -0,0 +1,1644 @@ +\section{Адресные пространства} +LibMCPU имеет независимые архитектурные адресные пространства (address spaces). Register file остаётся TLS-состоянием CPU; PROGRAM содержит исполняемый код; остальные сегменты являются пространствами объектов данных (data-object spaces) и используют целевое представление объектов LibMPU. + +\begin{center} +\begin{tabularx}{0.99\textwidth}{@{}L{21mm}L{15mm}L{81mm}Y@{}} +\toprule +\textbf{Пространство} & \textbf{SEG} & \textbf{Назначение} & \textbf{Доступ guest}\\ +\midrule +DATA & 0x0 & Глобальные/статические объекты и полноразмерные константы & R/W\\ +STACK & 0x1 & Вызовы, аргументы, automatic objects, spills & R/W\\ +PROGRAM & 0x2 & Машинный код; секции \code{.init/.text/.fini} & R + execute; запись запрещена\\ +CONTROL & 0x3 & Параметры управления запуском и модельным временем & R; runtime может изменять\\ +INPUT & 0x4 & Внешние возмущающие воздействия & R; runtime задаёт значения\\ +OUTPUT & 0x5 & Воздействие модели на среду / текущий выходной вектор & R/W\\ +TRACE & 0x6 & Текущий telemetry frame модели & R/W\\ +HEAP & 0x7 & Обнулённая runtime-область динамической памяти & R/W\\ +reserved & 0x8..0xF & Будущие адресные пространства & dereference fault\\ +Register file & -- & 512 KiB TLS; перекрывающиеся представления регистров & REG24\\ +\bottomrule +\end{tabularx} +\end{center} + +PROGRAM, DATA, STACK, CONTROL, INPUT, OUTPUT, TRACE и HEAP логически независимы и байтово адресуемы. Host backing каждого активного адресного пространства создаётся отдельным anonymous \code{mmap()}; файлы executable/image никогда не являются file-backed memory виртуального CPU. + +DATA и HEAP разделены. DATA является статическим пространством данных, формируемым образом executable (image-backed data space). HEAP является отдельным runtime-пространством с SEG=0x7, не имеет assembler payload и не расширяет DATA. Будущие malloc/free/realloc/calloc будут library-level ABI поверх HEAP, а не ISA instructions. + +Структуры CONTROL/INPUT/OUTPUT/TRACE не задаются CPU. ISA видит только bytes и права доступа. Форматы записей модели (model records), имена полей, типы и семантику модельного времени определит будущий C-подобный язык моделирования и runtime metadata. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,node distance=7mm and 8mm] + \node[draw=McpuBlue,fill=McpuGrayBg,minimum width=31mm,minimum height=12mm,align=center] (d) {DATA 0x0\\{\scriptsize R/W}}; + \node[draw=McpuBlue,fill=McpuGrayBg,right=of d,minimum width=31mm,minimum height=12mm,align=center] (s) {STACK 0x1\\{\scriptsize R/W}}; + \node[draw=McpuBlue,fill=McpuGreenBg,right=of s,minimum width=31mm,minimum height=12mm,align=center] (p) {PROGRAM 0x2\\{\scriptsize R + execute}}; + \node[draw=McpuBlue,fill=McpuGrayBg,right=of p,minimum width=31mm,minimum height=12mm,align=center] (c) {CONTROL 0x3\\{\scriptsize read-only}}; + \node[draw=McpuBlue,fill=McpuGrayBg,below=of d,minimum width=31mm,minimum height=12mm,align=center] (i) {INPUT 0x4\\{\scriptsize read-only}}; + \node[draw=McpuBlue,fill=McpuGrayBg,right=of i,minimum width=31mm,minimum height=12mm,align=center] (o) {OUTPUT 0x5\\{\scriptsize R/W}}; + \node[draw=McpuBlue,fill=McpuGrayBg,right=of o,minimum width=31mm,minimum height=12mm,align=center] (t) {TRACE 0x6\\{\scriptsize R/W}}; + \node[draw=McpuBlue,fill=McpuGrayBg,right=of t,minimum width=31mm,minimum height=12mm,align=center] (h) {HEAP 0x7\\{\scriptsize R/W}}; + \node[draw=McpuBlue2,dashed,fill=white,below=9mm of $(o)!0.5!(t)$,minimum width=70mm,minimum height=9mm] {0x8..0xF зарезервированы; разыменование запрещено}; +\end{tikzpicture} +\caption{Активная карта сегментов PTR64 и права доступа guest-программы.} +\end{figure} + +\section{Физическая адресация регистра: REG24} +Машинный операнд физического регистра имеет ровно 24 бита. Четыре старших бита кодируют физическую ширину представления регистра, оставшиеся двадцать --- байтовый адрес начала представления внутри единого регистрового файла. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,x=0.31cm,y=0.78cm] + \draw[fill=McpuGreenBg] (0,0) rectangle (4,1); + \draw[fill=McpuBlueBg] (4,0) rectangle (24,1); + \node at (2,0.5) {WIDTH}; + \node at (14,0.5) {BYTE ADDRESS}; + \node[above] at (2,1) {bits 23..20}; + \node[above] at (14,1) {bits 19..0}; + \draw[decorate,decoration={brace,mirror,amplitude=4pt}] (0,-0.18) -- (24,-0.18); + \node[below=6pt] at (12,-0.18) {REG24 = 3 bytes в PROGRAM}; +\end{tikzpicture} +\caption{Логическое устройство REG24.} +\end{figure} + +Три байта PROGRAM формируются следующим образом: первый байт содержит \code{WIDTH[3:0]} и \code{ADDRESS[19:16]}, второй --- \code{ADDRESS[15:8]}, третий --- \code{ADDRESS[7:0]}. Это машинное поле имеет канонический порядок байтов PROGRAM и не является host C bit-field. + +\begin{center} +\begin{tabularx}{0.78\textwidth}{@{}L{22mm}L{38mm}Y@{}} +\toprule +\textbf{WIDTH} & \textbf{Физическая ширина} & \textbf{Размер}\\ +\midrule +0x0 & 8 bit & 1 byte\\ +0x1 & 16 bit & 2 bytes\\ +0x2 & 32 bit & 4 bytes\\ +0x3 & 64 bit & 8 bytes\\ +0x4 & 128 bit & 16 bytes\\ +0x5 & 256 bit & 32 bytes\\ +0x6 & 512 bit & 64 bytes\\ +0x7 & 1024 bit & 128 bytes\\ +0x8 & 2048 bit & 256 bytes\\ +0x9 & 4096 bit & 512 bytes\\ +0xA & 8192 bit & 1024 bytes\\ +0xB & 16384 bit & 2048 bytes\\ +0xC & 32768 bit & 4096 bytes\\ +0xD & 65536 bit & 8192 bytes\\ +\bottomrule +\end{tabularx} +\end{center} + +Register file имеет 512 KiB, поэтому ADDRESS[19] обязан быть нулём. Views имеют natural alignment. Формула архитектуры остаётся REGISTER = WIDTH + BYTE ADDRESS. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + r8@0x00000 -> 00 00 00 + r16@0x00000 -> 10 00 00 + r4096@0x02000 -> 90 20 00 + r65536@0x7e000 -> D7 E0 00 +\end{lstlisting} + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Latex,x=0.92cm,y=0.85cm] + \foreach \x in {0,...,7} { + \draw (\x,0) rectangle ++(1,0.75); + \node[font=\ttfamily\scriptsize] at (\x+0.5,0.375) {+\x}; + } + \draw[very thick,color=McpuBlue] (0,-0.18) rectangle (8,0.93); + \node[anchor=east,color=McpuBlue] at (-0.15,0.38) {\texttt{r64@0x100}}; + \draw[very thick,color=McpuGreen] (4,-0.35) rectangle (8,1.10); + \node[anchor=west,color=McpuGreen] at (8.15,0.38) {\texttt{r32@0x104}}; + \node[below=5mm] at (4,0) {одни и те же физические байты register file}; +\end{tikzpicture} +\caption{Представления (views) разной ширины могут перекрываться.} +\end{figure} + +\section{r и c: разные смыслы одного REG24} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + r64@0x02000 ; 64 физических бита, integer/real смысл задаёт opcode + c64@0x02000 ; те же 64 физических бита, complex semantic view +\end{lstlisting} + +Обе записи имеют одинаковый REG24. Для c<bits> число bits означает полную физическую ширину; каждая real-компонента имеет половинную ширину. + +\begin{center} +\begin{tabularx}{0.78\textwidth}{@{}L{34mm}L{38mm}Y@{}} +\toprule +\textbf{Complex view} & \textbf{Физический размер} & \textbf{Компоненты}\\ +\midrule +\code{c64} & 64 bit & 2 x real32\\ +\code{c128} & 128 bit & 2 x real64\\ +\code{c4096} & 4096 bit & 2 x real2048\\ +\code{c32768} & 32768 bit & 2 x real16384\\ +\code{c65536} & 65536 bit & 2 x real32768\\ +\bottomrule +\end{tabularx} +\end{center} + +\section{Канонический порядок байтов PROGRAM} +OPCODE16, REG24, IMM64, ADDR64, CODE64 и OFF64 кодируются в PROGRAM MSB-first, независимо от host endian и target data profile. Архитектурные data objects (register file, DATA, STACK и остальные data spaces) образуют отдельный byte-order domain и используют representation, совместимое с настроенной LibMPU. Полная нормативная модель приведена в разделе 31. + +PROGRAM serialization и architectural data-object representation - разные уровни. Decoder собирает многобайтовые поля явно как MSB-first; произвольный host-cast packed C-структурой не является частью формата. + +\section{Непосредственные числовые константы} +Inline numeric payload всегда равен 64 битам; opcode задаёт интерпретацию. Непосредственная форма предназначена только для значений, которые точно представимы в машинном immediate-формате соответствующей инструкции. Ассемблер не имеет права молча терять значащие биты/цифры при преобразовании textual literal в immediate. + +Exact-immediate rule. Если textual literal не представим точно в immediate source format, + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mcpu-as завершает сборку с ошибкой и рекомендует создать полноразмерный объект +\end{lstlisting} + +через .integer/.real/.complex в DATA и загрузить его инструкцией LOAD. + +\subsection{LDI - Integer} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LDI r<bits>@addr, integer +\end{lstlisting} + +OPCODE16 + REG24 + IMM64 = 13 bytes + +IMM64 - signed 64-bit two's-complement integer. Для destination шириной 64 бит и более literal обязан точно помещаться в signed integer64. Для destination уже 64 бит literal дополнительно обязан помещаться в signed диапазон именно этой ширины: assembler не генерирует заведомо теряющее значение icvt(). + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; корректно + LDI r8@0x00000, -1 + LDI r32@0x00010, 1000000 + LDI r4096@0x00100, 9223372036854775807 + + ; ошибка: +255 не является signed integer8 value + LDI r8@0x00000, 255 + + ; ошибка: literal шире signed integer64 source format + LDI r128@0x00100, 9223372036854775808 +\end{lstlisting} + +Для большого целого используется DATA: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 16 +\end{lstlisting} + +wide\_integer: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 128, 9223372036854775808 + .text + LOAD r128@0x00100, wide_integer +\end{lstlisting} + +Типичная диагностика: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mcpu-as: error: + line N: LDI immediate is not exactly representable as integer64; +\end{lstlisting} + +use .integer <bits>, value in DATA and LOAD + +\subsection{LDR - Real} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LDR r32@0x02000, -17.0 + LDR r4096@0x04000, 2.0 +\end{lstlisting} + +OPCODE16 + REG24 + REAL64 = 13 bytes + +Machine payload LDR всегда binary64. Но assembler применяет более строгую проверку в зависимости от destination: для r32 textual literal обязан быть точно представим как real32; для r64 и всех более широких Real-register views - точно представим как real64. После этой проверки runtime по-прежнему выполняет r\_cvt() из embedded real64 к физической ширине destination. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; корректно: значения точно представимы + LDR r32@0x02000, -17.0 + LDR r4096@0x04000, 2.0 + + ; ошибка: 0.1 не имеет точного binary32 representation + LDR r32@0x02000, 0.1 + + ; ошибка: high-precision decimal сначала потерял бы точность в binary64 + LDR r4096@0x04000, 3.14243987129849274029174912694696912849214e-286 +\end{lstlisting} + +Диагностика для r32: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mcpu-as: error: + line N: LDR immediate is not exactly representable as real32; +\end{lstlisting} + +use .real <bits>, value in DATA and LOAD + +Диагностика для r64 и шире: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mcpu-as: error: + line N: LDR immediate is not exactly representable as real64; +\end{lstlisting} + +use .real <bits>, value in DATA and LOAD + +Правильная форма для произвольной высокоточной константы: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 16 +\end{lstlisting} + +pi\_like: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .real 4096, 3.14243987129849274029174912694696912849214e-286 + .text + LOAD r4096@0x04000, pi_like +\end{lstlisting} + +.real передаёт textual literal непосредственно ascii\_to\_real() LibMPU с precision конечного DATA-object; промежуточного binary64 здесь нет. + +\subsection{LDC - Complex} +В машинном коде inline-комплекс состоит из двух компонент binary32: +\begin{center} +\begin{tabularx}{0.72\textwidth}{@{}Y Y@{}} +\toprule +\textbf{REAL} & \textbf{IMAG}\\ +\midrule +float32, 32 bits & float32, 32 bits\\ +\bottomrule +\end{tabularx} +\end{center} + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LDC c64@0x02000, -17.0r0+32.0j0 + LDC c4096@0x04000, 2.0r0-0.5j0 +\end{lstlisting} + +Формат инструкции: OPCODE16 + REG24 + COMPLEX64 = 13 bytes. + +Минимальный complex destination - c64, то есть две real32 components. Inline LDC всегда содержит 2 x binary32, поэтому каждая textual component обязана быть точно представима как real32 независимо от ширины destination. После декодирования CPU расширяет обе real32 components через LibMPU к половине физической ширины c-view и вызывает c\_gen\_complex(). + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; корректно + LDC c64@0x02000, -17.0r0+32.0j0 + LDC c4096@0x04000, 2.0r0-0.5j0 + + ; ошибка: real component 0.1 не представима точно как real32 + LDC c128@0x02000, 0.1r0+32.0j0 + + ; ошибка ещё на разборе регистра: c32 запрещён + LDC c32@0x02000, 1.0r0+2.0j0 +\end{lstlisting} + +Диагностика потери точности: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mcpu-as: error: + line N: LDC immediate component is not exactly representable as real32; +\end{lstlisting} + +use .complex <bits>, real, imag in DATA and LOAD + +Для широкого complex object: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 16 +\end{lstlisting} + +wide\_z: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .complex 4096, 3.14243987129849274029174912694696912849214e-286, -0.125 + .text + LOAD c4096@0x04000, wide_z +\end{lstlisting} + +.complex формирует components непосредственно в требуемой LibMPU precision; LDC для таких значений использовать нельзя. + +\subsection{Почему это assembler error, а не runtime rounding} +Immediate является частью PROGRAM image и уже после сборки не содержит исходный текст literal. Если assembler сначала округлит длинную десятичную запись до binary64/binary32, executor уже не способен узнать, что информация была потеряна. Поэтому exactness проверяется до emission bytecode: textual literal разбирается LibMPU с высокой precision, конвертируется в immediate precision и обратно, после чего значения сравниваются. Только exact round-trip допускается к emission. + +Это правило относится к immediate-формам LDI/LDR/LDC. Полноразмерные DATA directives намеренно выполняют conversion непосредственно к объявленной ширине объекта; обычное математическое округление decimal->binary в этой целевой precision является частью создания самого объекта. + +\subsection{Conversion-class instructions сохраняют PSW} +Некоторые LibMPU conversion helpers изменяют внутренние arithmetic/status flags как побочный эффект своей реализации. Например, icvt() влияет на часть integer flags. Для LibMCPU это нежелательно: преобразование значения не должно неожиданно уничтожать condition state, вычисленное предыдущей инструкцией. + +Нормативное правило. Инструкции LDI, LDR, LDC, ICVT, RCVT, ITOR, UITOR, RTOI, RTOUI, CCVT и CRTOC являются PSW-neutral. Перед вызовом LibMPU conversion routine executor сохраняет PSW[15:0], после завершения conversion восстанавливает прежнее значение. HF/TF/IF/MODE при этом вообще не изменяются. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + saved = PSW[15:0] + icvt(...) ; или r_cvt(), c_cvt(), ltor(), ... +\end{lstlisting} + +PSW[15:0] = saved + +Внутренняя реализация LibMCPU намеренно не включает private header LibMPU и не зависит от private macro \_\_MPU\_FLAGS. Сохранение/восстановление делается через уже существующий public PSW facade LibMCPU, который собирает и восстанавливает LibMPU flags через публичные accessor functions. Архитектурно результат эквивалентен сохранению \_\_MPU\_FLAGS вокруг conversion. + +Это правило относится только к операциям, смысл которых - загрузить или преобразовать значение. Обычные arithmetic instructions продолжают публиковать нормальные LibMPU flag effects и поэтому не выполняют такого save/restore. + +\section{DATA, ADDR64 и NULL guard} +ADDR64 остаётся 64-битным byte offset внутри DATA для прямых инструкций LOAD/STORE. Это не PTR64. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LOAD REG24, ADDR64 ; DATA -> register file + STORE ADDR64, REG24 ; register file -> DATA +\end{lstlisting} + +Обе формы занимают 13 байт. Размер копии задаётся физической шириной REG24. + +\subsection{Нулевой PTR64 и первые 16 байт DATA} +ABI определяет нулевой указатель как ровно 0x0000000000000000. Битово это совпадает с комбинацией SEG=DATA, OFFSET=0. Поэтому executor отдельно распознаёт all-zero PTR64; при попытке разыменования LOADP, STOREP, BMOV и BMOVI возвращают CPU\_EXEC\_POINTER\_FAULT. + +PTR64 NULL = 0x0000000000000000 + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + DATA 0x0000 .. 0x000f reserved NULL guard + DATA 0x0010 .. ordinary assembler objects +\end{lstlisting} + +Assembler всегда начинает DATA image с 16 нулевых guard bytes. Поэтому обычная DATA label не может иметь PTR64 value zero и первый допустимый object offset сразу кратен 16. + +Прямой ADDR64 и NULL. NULL относится к PTR64. Низкоуровневые прямые LOAD/STORE технически адресуют DATA по raw ADDR64, но ABI считает DATA[0..15] зарезервированной областью и conforming assembler/program не размещает там объекты. + +\subsection{.align выравнивает guest offset} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 16 +\end{lstlisting} + +x: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .real 4096, 0.0 +\end{lstlisting} + +.align N двигает только текущий guest DATA offset до следующего кратного N значения. Директивы .integer/.real/.complex сами по себе не вставляют никакого padding и пишут + +объект точно с текущего offset. Это принципиально: assembler способен описывать как обычные ABI-layouts, так и packed binary layouts. + +\subsection{Code pointers in DATA} +DATA может содержать PROGRAM pointers для indirect control flow. Директива .cptr label занимает 8 байт, резервирует PTR64 object и после полной сборки .text разрешает CODE label в PROGRAM byte offset. Она не добавляет implicit alignment. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 8 +\end{lstlisting} + +handlers: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .cptr handler0 + .cptr handler1 +\end{lstlisting} + +Получившийся object можно загрузить обычным LOADP r64,... и передать в JMPP/CALLP. Forward CODE labels поддерживаются assembler fixup mechanism. + +\section{Полноразмерные константы} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 16 +\end{lstlisting} + +huge\_integer: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 65536, 12345678901234567890... +\end{lstlisting} + +high\_precision\_real: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .real 16384, 1.234567890123456789... +\end{lstlisting} + +high\_precision\_complex: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .complex 32768, 1.234567890123456789..., -2.5 + + .text + LOAD r65536@0x00000, huge_integer + LOAD r16384@0x02000, high_precision_real + LOAD c32768@0x08000, high_precision_complex +\end{lstlisting} + +Тип, размер, internal padding/alignment C-like объектов известны assembler/compiler. CPU копирует байты и не хранит runtime type tag. + +\section{Предопределённые математические константы} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + RCONST r4096@0x02000, PI + RCONST r16384@0x04000, E + RCONST r128@0x08000, SQRT2 +\end{lstlisting} + +OPCODE16 + REG24 + CONST8 = 6 bytes + +LibMPU строит значение непосредственно с требуемой точностью. Основные группы констант перечислены в приложении: ZERO/HALF/ONE/TWO/TEN, PI/E/LN2/LN10, кратные PI, SQRT2/SQRT3/SQRTPI, GOLDENRATIO, EULERGAMMA и CATALAN. + +\section{OPCODE16 и сигнатуры} +OPCODE16 полностью определяет количество и тип следующих операндов. Отдельного format-byte нет. + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{22mm}L{52mm}Y@{}} +\toprule +\textbf{Диапазон} & \textbf{Назначение} & \textbf{Примеры}\\ +\midrule +0x00xx & CPU/system и PSW status & NOP, HALT, SETF/CLRF, CLRCPUF/CLRMPUF\\ +0x01xx & data movement / построение адресов & MOV, LDI, LDR, LDC, LOAD, STORE, LEA, LEASP, LOADP, STOREP\\ +0x10xx & integer & IADD, ISUB, ICMP, ...\\ +0x20xx & real & RADD, RSIN, RLOG, ...\\ +0x30xx & complex & CADD, CEXP, CPOW, ...\\ +0x40xx & control flow & JMP/JMPP, JSET/JCLR, CALL/CALLP, RET\\ +0x50xx & stack/frame & PUSH, POP, LDSP, STSP, ADJSP, PUSHFP, SETFP, ...\\ +\bottomrule +\end{tabularx} +\end{center} + +\begin{longtable}{@{}L{23mm}L{30mm}L{105mm}@{}} +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Формат операндов}\\ +\midrule +\endfirsthead +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Формат операндов}\\ +\midrule +\endhead +0x0010 & SETF & PSWBIT8 (AF..INXF)\\ +0x0011 & CLRF & PSWBIT8 (AF..INXF)\\ +0x0012 & CLRCPUF & ---\\ +0x0013 & CLRMPUF & ---\\ +0x0120 & LOAD & R/C, ADDR64\\ +0x0121 & STORE & ADDR64, R/C\\ +0x0130 & LEA & r64, ADDR64\\ +0x0131 & LEASP & r64, OFF64\\ +0x0132 & LOADP & R/C, r64(PTR64)\\ +0x0133 & STOREP & r64(PTR64), R/C\\ +0x0140 & PADD & r64(dst), r64(ptr), r64(delta)\\ +0x0141 & PADDI & r64(dst), r64(ptr), OFF64\\ +0x0142 & PSUB & r64(dst), r64(ptr), r64(delta)\\ +0x0143 & PSUBI & r64(dst), r64(ptr), OFF64\\ +0x0144 & PDIFF & r64(dst), r64(ptr1), r64(ptr2)\\ +0x0150 & BMOV & r64(dst PTR64), r64(src PTR64), r64(byte_count)\\ +0x0151 & BMOVI & r64(dst PTR64), r64(src PTR64), IMM64 byte_count\\ +0x4000 & JMP & CODE64\\ +0x4001/02 & JSET/JCLR & PSWBIT8, CODE64\\ +0x4003 & JMPP & r64(PROGRAM PTR64)\\ +0x4010 & CALL & CODE64\\ +0x4011 & RET & ---\\ +0x4012 & CALLP & r64(PROGRAM PTR64)\\ +0x5000/01 & PUSH/POP & R/C\\ +0x5010/11 & LDSP/STSP & R/C,OFF64 / OFF64,R/C\\ +0x5020 & ADJSP & OFF64\\ +\bottomrule +\end{longtable} + +\section{Явное управление флагами PSW} +Arithmetic/status domain PSW намеренно разделён на два byte-range. \code{PSW[7:0]} --- CPU flags, \code{PSW[15:8]} --- MPU mathematical flags. Execution/control state начинается выше этого диапазона и не является writable через обычные flag instructions. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + SETF CF ; PSW[CF] = 1 + CLRF AF ; PSW[AF] = 0 + CLRCPUF ; PSW[7:0] = 0 + CLRMPUF ; PSW[15:8] = 0 +\end{lstlisting} + +\code{SETF} и \code{CLRF} принимают те же symbolic flag names AF, CF, OF, SF, PF, ZF, RF, VF, DOMF, SNGF, OVFF, UDFF, TLSF, PLSF, INDF, INXF, которые используются branch selector namespace, но write contract намеренно заканчивается на bit 15. Попытка \code{SETF HF}, \code{CLRF TF} или \code{SETF IF} является assembler error; decoder/executor независимо отвергает такой bytecode как invalid operand. MODE многобитовое поле и отдельного selector не имеет. + +Каждая single-flag instruction изменяет ровно выбранный bit и сохраняет остальные биты PSW. \code{CLRCPUF} использует маску \code{0x000000ff}, \code{CLRMPUF} --- \code{0x0000ff00}; обе операции сохраняют HF/TF/IF/MODE и reserved bits. + +Практически это устраняет необходимость искусственно выполнять арифметическую операцию только ради получения заданного initial carry: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + CLRF CF + IADC r64@0x00000, r64@0x00010, r64@0x00018 + IADC r64@0x00008, r64@0x00020, r64@0x00028 + + SETF CF + ISBB r64@0x00030, r64@0x00040, r64@0x00048 +\end{lstlisting} + +\code{IADC}, \code{ISBB} и rotate-through-carry instructions продолжают использовать установленную LibMPU flag semantics; \code{SETF/CLRF} не переопределяют их арифметику, а только задают входное flag state. + +\section{Ограничения разрядности} + +\begin{center} +\begin{tabularx}{0.84\textwidth}{@{}L{30mm}L{62mm}Y@{}} +\toprule +\textbf{Класс} & \textbf{Простые операции} & \textbf{Трансцендентные}\\ +\midrule +Integer & до 65536 bit & ---\\ +Real & до 65536 bit & до 16384 bit\\ +Complex & до c65536 = 2 x real32768 & до c32768 = 2 x real16384\\ +\bottomrule +\end{tabularx} +\end{center} + +\section{PC, CODE64 и direct/indirect control flow} +PC - 64-битный byte offset в PROGRAM. CODE64 - такой же raw PROGRAM offset внутри machine code, encoded canonical MSB-first. Прямые переходы получают target из bytecode, а indirect forms получают target через обычный 64-bit register, содержащий PROGRAM PTR64. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + обычная инструкция: PC = old_PC + instruction_length + JMP CODE64: PC = target + JSET/JCLR taken: PC = target + JSET/JCLR not taken: PC = next_pc + JMPP r64(PTR64): PC = OFFSET(ptr), если SEG=PROGRAM + CALL CODE64: push next_pc; PC = target + CALLP r64(PTR64): push next_pc; PC = OFFSET(ptr), если SEG=PROGRAM +\end{lstlisting} + +JZ/JNZ/JC/JNC/JO/JNO/JS/JNS являются assembler aliases для JSET/JCLR над конкретными PSW bit. + +\subsection{Зачем нужны JMPP и CALLP} +JMP и CALL кодируют конкретный CODE64 target прямо внутри инструкции. Это удобно для statically known labels. JMPP и CALLP нужны тогда, когда target выбирается во время исполнения: function pointer, callback, таблица обработчиков, virtual dispatch, jump table или runtime-selected routine. + +Code/function pointer ABI. Указатель на executable code является обычным PTR64 с SEG = PROGRAM (0x2). Offset - byte address внутри текущего PROGRAM. Отдельного архитектурного типа function-pointer не существует: это 64-битное значение в обычном r64. + +\subsection{JMPP - indirect jump} +\begin{center} +\begin{tabularx}{0.86\textwidth}{@{}L{24mm}L{45mm}Y@{}} +\toprule +\textbf{Код операции} & \textbf{Source syntax} & \textbf{Машинный формат / длина}\\ +\midrule +0x4003 & \code{JMPP r64@addr} & OPCODE16 + REG24 = 5 bytes\\ +\bottomrule +\end{tabularx} +\end{center} + +Source register содержит полный PTR64. Executor сначала читает 8 байт register view и проверяет: + +ptr != NULL SEG(ptr) == PROGRAM OFFSET(ptr) < program.size + +При успехе выполняется только: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + PC = OFFSET(ptr) +\end{lstlisting} + +SP, FP, STACK, PSW и сам source register не изменяются. Ошибка segment selector, NULL или offset за пределами текущего PROGRAM даёт CPU\_EXEC\_POINTER\_FAULT, причём PC остаётся прежним. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; r64@0x00100 = PTR64(PROGRAM, target_offset) + JMPP r64@0x00100 +\end{lstlisting} + +Для r64@0x00100 REG24 равен 30 01 00, поэтому machine bytes инструкции: + +40 03 30 01 00 + +Instruction boundary metadata нет. CPU проверяет только PROGRAM segment и byte range. Если runtime-created pointer указывает в середину другой инструкции, PTR64 может быть range-valid; следующая decode operation определит, образует ли последовательность bytes допустимую инструкцию. Assembler-generated code labels и + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .cptr label естественно указывают на начало помеченной assembler instruction. +\end{lstlisting} + +\subsection{CALLP - indirect call} +\begin{center} +\begin{tabularx}{0.86\textwidth}{@{}L{24mm}L{45mm}Y@{}} +\toprule +\textbf{Код операции} & \textbf{Source syntax} & \textbf{Машинный формат / длина}\\ +\midrule +0x4012 & \code{CALLP r64@addr} & OPCODE16 + REG24 = 5 bytes\\ +\bottomrule +\end{tabularx} +\end{center} + +CALLP использует тот же CALL ABI, что и direct CALL CODE64. Различается только источник target. После проверки PROGRAM PTR64 и наличия места в STACK вычисляется: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + next_pc = old_PC + 5 + new_sp = old_SP - 8 +\end{lstlisting} + +и только после всех проверок происходит архитектурное изменение: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + STACK[new_sp .. new_sp+7] = next_pc ; raw 64-bit CODE64 offset + SP = new_sp + PC = OFFSET(ptr) +\end{lstlisting} + +Возвратный адрес не является PTR64. CALLP, как и CALL, помещает в STACK raw 64-bit PROGRAM byte offset. Поэтому существующий RET не различает direct и indirect call: он снимает тот же CODE64 и устанавливает PC. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; r64@0x00100 = PTR64(PROGRAM, function_offset) + CALLP r64@0x00100 + ; execution resumes here after RET +\end{lstlisting} + +Для того же r64@0x00100 machine bytes: + +40 12 30 01 00 + +CALLP не меняет PSW и FP. Register file считается volatile across CALL по уже принятому ABI; это соглашение caller/callee, а не side effect самой инструкции. + +\subsection{Atomic fault semantics CALLP} +CALLP сначала проверяет pointer, текущий PROGRAM range и возможность разместить 8-byte адрес возврата (return PC) в STACK. До успешного завершения всех проверок не изменяются PC, SP и STACK. Поэтому не существует состояния "return address уже записан, но переход не произошёл". + +\begin{center} +\begin{tabularx}{0.97\textwidth}{@{}L{58mm}L{38mm}Y@{}} +\toprule +\textbf{Условие} & \textbf{Результат} & \textbf{Архитектурное состояние}\\ +\midrule +Operand не является физическим 64-bit register & WIDTH/operand fault & без изменений\\ +NULL или любой non-PROGRAM PTR64 & POINTER\_FAULT & без изменений\\ +PROGRAM offset $\ge$ \code{program.size} & POINTER\_FAULT & без изменений\\ +Недостаточно STACK для адрес возврата (return PC) & STACK\_FAULT & без изменений\\ +Успех & CPU\_EXEC\_BRANCH & адрес возврата (return PC) помещён в STACK; SP/PC изменены\\ +\bottomrule +\end{tabularx} +\end{center} + +\subsection{Assembler directive .cptr} +Чтобы таблицы function/code pointers можно было собирать непосредственно в DATA, assembler поддерживает: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .cptr CODE_LABEL +\end{lstlisting} + +Директива занимает ровно 8 байт и после разрешения label создаёт: + +PTR64(SEG=PROGRAM, OFFSET=byte\_address\_of(CODE\_LABEL)) + +Forward references разрешены. .cptr не вставляет automatic alignment; при необходимости таблица явно начинается с .align 8 или более сильного alignment. + +Каноническое spelling - .cptr. Историческое .codeptr assembler продолжает принимать как compatibility alias, но новые исходники должны использовать .cptr. Имя .label намеренно не используется: label определяется синтаксисом name:, а эта directive создаёт 8-byte code-pointer object. + +Не путать с bytecode field encoding. OPCODE16/CODE64/IMM64 внутри PROGRAM имеют canonical MSB-first encoding. .cptr находится в DATA и записывает architectural object representation PTR64, то есть те же 8 байт, которые затем LOADP r64,... помещает в 64-bit register view. + +\subsection{Полный пример: indirect call + indirect jump} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 8 +\end{lstlisting} + +dispatch: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .cptr indirect_function + .cptr done + + .text + LEA r64@0x00000, dispatch + LOADP r64@0x00008, r64@0x00000 + CALLP r64@0x00008 + + PADDI r64@0x00000, r64@0x00000, 8 + LOADP r64@0x00010, r64@0x00000 + JMPP r64@0x00010 + + ; unreachable if JMPP works + LDI r64@0x00020, 99 + HALT +\end{lstlisting} + +indirect\_function: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LDI r64@0x00020, 42 + RET +\end{lstlisting} + +done: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + HALT +\end{lstlisting} + +Первый table element вызывает функцию через CALLP. RET возвращает на инструкцию после 5-byte CALLP. Затем pointer arithmetic выбирает второй element, LOADP загружает следующий PROGRAM PTR64, а JMPP переносит PC к done. Никакой специальной SWITCH/JUMP-TABLE инструкции не требуется. + +\subsection{Jump-table pattern} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; index/range validation and index*8 scaling are done by surrounding code + ; r64@byte_off already contains the byte offset of the selected 8-byte entry + LEA r64@table_ptr, table + PADD r64@entry_ptr, r64@table_ptr, r64@byte_off + LOADP r64@target, r64@entry_ptr + JMPP r64@target +\end{lstlisting} + +Это normative low-level pattern для будущего assembler/compiler. Проверка index range и перевод element index в byte offset выполняются до вычисления/использования table entry. CPU остаётся независимым от high-level конструкции switch. + +\section{STACK и SP} + +\term{STACK}{стек} --- отдельное архитектурное адресное пространство MCPU. Оно растёт \textbf{к меньшим адресам}: при выделении памяти значение SP уменьшается, при освобождении --- увеличивается. После создания пустого стека + +\begin{center} +\code{SP = FP = stack_size}. +\end{center} + +SP хранит не PTR64, а обычный 64-битный \textbf{byte offset внутри STACK}. Поэтому указатель на объект стека получается явно через \code{LEASP}/\code{LEAFP}; сам SP не содержит selector сегмента. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,x=1cm,y=1cm] + \draw[McpuBlue,thick] (2.0,0.3) rectangle (8.3,5.7); + \fill[McpuGrayBg] (2.02,0.32) rectangle (8.28,2.25); + \fill[McpuGreenBg] (2.02,2.25) rectangle (8.28,5.68); + \draw[McpuBlue,very thick] (1.65,2.25) -- (8.85,2.25); + \node[anchor=west,color=McpuBlue] at (8.95,2.25) {SP}; + \node[color=McpuBlue] at (5.15,4.05) {свободная область STACK}; + \node[color=McpuBlue2] at (5.15,1.25) {занятые данные}; + \node[anchor=west,color=McpuBlue2] at (0.0,5.65) {малые адреса}; + \node[anchor=west,color=McpuBlue2] at (0.0,-0.12) {большие адреса; конец STACK = \code{stack_size}}; + \draw[->,McpuGreen,very thick] (9.8,3.95) -- (9.8,2.55); + \node[anchor=west,color=McpuGreen] at (10.0,3.25) {PUSH}; + \draw[->,McpuBlue,very thick] (10.9,1.0) -- (10.9,2.0); + \node[anchor=west,color=McpuBlue] at (11.1,1.45) {POP}; +\end{tikzpicture} +\caption{Стек растёт к меньшим адресам. PUSH уменьшает SP, POP увеличивает SP.} +\end{figure} + +Такое направление роста удобно для ABI вызовов: caller формирует блок аргументов (argument block), затем CALL кладёт адрес возврата (return PC) непосредственно \textbf{перед} ним, а callee может обращаться к аргументам по положительным смещениям от входного SP или от стабильного FP. + +\subsection{Низкоуровневые операции STACK} + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{24mm}L{56mm}Y@{}} +\toprule +\textbf{Инструкция} & \textbf{Изменение SP} & \textbf{Смысл}\\ +\midrule +\code{PUSH R} & \code{SP -= sizeof(R)} & Записать представление регистра в \code{STACK[SP]}.\\ +\code{POP R} & \code{SP += sizeof(R)} после чтения & Прочитать значение из текущей вершины стека.\\ +\code{LDSP R,off} & не изменяется & Прочитать объект по адресу \code{SP + off}.\\ +\code{STSP off,R} & не изменяется & Записать объект по адресу \code{SP + off}.\\ +\code{ADJSP off} & \code{SP += signed(off)} & Явно зарезервировать или освободить область стека.\\ +\bottomrule +\end{tabularx} +\end{center} + +CPU не добавляет скрытого padding или alignment. ABI требует 16-byte alignment перед ABI-conforming CALL, но конкретные \code{ADJSP}, padding slots и offsets формирует assembler/compiler. Низкоуровневые операции остаются byte-addressable и допускают unaligned addresses, если конкретное ABI-правило не требует большего выравнивания. + +\begin{principle} +SP --- не обычная переменная. Каждое изменение SP меняет границу живой stack storage. Выделение и освобождение памяти должны быть симметричны, а любой PTR64 на освобождённую stack-область становится семантически недействительным. +\end{principle} + +\section{CALL/RET, блок аргументов (argument block) и PSW calling convention} + +Нормативный ABI отделяет \textbf{блок аргументов (argument block)}, принадлежащий caller-у, от 8-байтового адрес возврата (return PC), который автоматически добавляет CALL. Непосредственно перед ABI-conforming CALL значение SP кратно 16. Caller заранее резервирует цельный блок аргументов (argument block), размещает в нём hidden-result pointer (если он нужен) и explicit arguments, затем выполняет CALL. + +\subsection{Размещение блок аргументов (argument block)} + +Argument block строится от меньших offsets к большим в порядке параметров функции. Если функция возвращает aggregate, hidden result PTR64 занимает slot 0. Для каждого аргумента: + +\begin{codebox} +\begin{verbatim} +offset = align_up(offset, alignof(argument)) +argument.offset = offset +offset += sizeof(argument) + +argument_block_size = align_up(offset, 16) +\end{verbatim} +\end{codebox} + +Base блок аргументов (argument block) перед CALL должен быть выровнен на 16 bytes. CPU не знает типов аргументов и не вставляет padding автоматически: это правило assembler/compiler ABI. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,x=1cm,y=1cm] + \def\xL{2.4}\def\xR{8.3} + \fill[McpuGreenBg] (\xL,4.8) rectangle (\xR,5.6); + \fill[McpuGrayBg] (\xL,3.9) rectangle (\xR,4.8); + \fill[McpuGrayBg] (\xL,3.0) rectangle (\xR,3.9); + \fill[yellow!10] (\xL,2.2) rectangle (\xR,3.0); + \draw[McpuBlue,thick] (\xL,2.2) rectangle (\xR,5.6); + \foreach \y in {3.0,3.9,4.8} {\draw[McpuBlue2] (\xL,\y)--(\xR,\y);} + \node at (5.35,5.2) {адрес возврата (return PC) (8 bytes)}; + \node at (5.35,4.35) {блок аргументов (argument block) +0}; + \node at (5.35,3.45) {блок аргументов (argument block) +\dots}; + \node[color=McpuBlue2] at (5.35,2.6) {tail padding до 16 bytes}; + \node[anchor=west,color=McpuBlue] at (8.65,5.2) {SP + 0}; + \node[anchor=west,color=McpuBlue] at (8.65,4.35) {SP + 8}; + \node[anchor=west,color=McpuBlue] at (8.65,3.45) {SP + 8 + \dots}; + \node[anchor=west,color=McpuBlue2] at (1.0,6.05) {состояние callee сразу после CALL}; + \draw[decorate,decoration={brace,amplitude=5pt},McpuGreen] (1.95,4.8)--(1.95,2.2) + node[midway,left=7pt,align=right,color=McpuGreen]{argument\\block caller-а}; + \node[anchor=west,color=McpuBlue2] at (2.4,1.55) {base блок аргументов (argument block) = caller SP перед CALL; поэтому он выровнен на 16 bytes}; + \node[anchor=west,color=McpuBlue2] at (2.4,1.05) {после PUSHFP; SETFP тот же base находится по FP + 16}; +\end{tikzpicture} +\caption{CALL добавляет адрес возврата (return PC) перед уже сформированным блок аргументов (argument block).} +\end{figure} + +После CALL callee видит адрес возврата (return PC) по \code{SP+0}, а начало блок аргументов (argument block) --- по \code{SP+8}. RET снимает \textbf{только} адрес возврата (return PC). Весь блок аргументов (argument block) после возврата освобождает caller. + +\subsection{Простые PUSH остаются допустимым сокращением} + +Старый right-to-left PUSH spelling остаётся корректным там, где он создаёт тот же layout и оставляет SP кратным 16. Например два \code{r64} занимают ровно 16 bytes: + +\begin{lstlisting} +PUSH r64@arg2 +PUSH r64@arg1 +CALL f +ADJSP 16 +\end{lstlisting} + +Для одного \code{r64} блок аргументов (argument block) всё равно должен занимать 16 bytes: + +\begin{lstlisting} +ADJSP -16 +STSP 0, r64@arg1 +CALL f +ADJSP 16 +\end{lstlisting} + +\subsection{CALL, RET, FP и PSW} + +CALL и CALLP помещают 64-bit next PC в STACK; RET снимает его обратно в PC. Return address является raw PROGRAM byte offset (CODE64), а не PTR64. Регистра RA/LR нет. + +CALL/RET не изменяют FP и не сохраняют PSW автоматически. Arithmetic/status bits считаются caller-clobbered согласно принятому ABI, а persistent control bits должны быть восстановлены callee перед обычным возвратом, если callee их изменял. Сохранение FP --- отдельная обязанность функций, которые выбрали frame-pointer convention. + +\begin{principle} +CPU выполняет только механические действия CALL/RET. Типы аргументов, padding, hidden-result slot, владение блок аргументов (argument block) и сохранение долгоживущего состояния --- контракт ABI между caller, callee и compiler. +\end{principle} + +\section{Return Value Area} + +Верхние 8192 bytes register file зарезервированы ABI для канонического scalar return value. Это не отдельный физический регистр: результат размещается в обычном перекрывающемся register file так, чтобы \textbf{старший конец выбранного physical view совпадал с концом register file}. + +\begin{center} +\begin{tabularx}{0.82\textwidth}{@{}L{34mm}L{45mm}Y@{}} +\toprule +\textbf{View} & \textbf{Physical size} & \textbf{Канонический return register}\\ +\midrule +\code{r8} & 1 byte & \code{r8@0x7ffff}\\ +\code{r16} & 2 bytes & \code{r16@0x7fffe}\\ +\code{r32} & 4 bytes & \code{r32@0x7fffc}\\ +\code{r64} & 8 bytes & \code{r64@0x7fff8}\\ +\code{r4096} & 512 bytes & \code{r4096@0x7fe00}\\ +\code{r65536} & 8192 bytes & \code{r65536@0x7e000}\\ +\code{c64} & 8 bytes & \code{c64@0x7fff8}\\ +\code{c4096} & 512 bytes & \code{c4096@0x7fe00}\\ +\code{c65536} & 8192 bytes & \code{c65536@0x7e000}\\ +\bottomrule +\end{tabularx} +\end{center} + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,x=1cm,y=1cm] + \draw[McpuBlue,thick] (0.8,1.2) rectangle (11.5,2.55); + \fill[McpuGrayBg] (0.82,1.22) rectangle (9.5,2.53); + \fill[McpuGreenBg] (9.5,1.22) rectangle (11.48,2.53); + \draw[McpuBlue2] (9.5,1.2)--(9.5,2.55); + \node at (5.1,1.88) {обычная volatile register area}; + \node[align=center,color=McpuBlue] at (10.48,1.88) {Return Value\\Area, 8 KiB}; + \node[anchor=north,color=McpuBlue2] at (0.8,1.05) {0x00000}; + \node[anchor=north,color=McpuBlue2] at (9.5,1.05) {0x7e000}; + \node[anchor=north,color=McpuBlue2] at (11.5,1.05) {0x80000}; + \draw[->,McpuGreen,very thick] (1.3,3.15)--(8.95,3.15); + \node[color=McpuGreen,above] at (5.1,3.15) {automatic register allocation растёт вверх}; +\end{tikzpicture} +\caption{Верхние 8 KiB register file зарезервированы под канонический scalar result.} +\end{figure} + +Return Value Area является volatile across CALL. Если caller должен сохранить результат через следующий вызов функции, он обязан скопировать его в собственный register slot или memory object. + +\section{PTR64: сегментированный виртуальный} +указатель PTR64 является 64-битным архитектурным указателем, но не host pointer. Старшие 4 бита выбирают segment selector, младшие 60 - byte offset внутри сегмента. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + SEG BYTE OFFSET + bits 63..60 bits 59..0 + 4 bits 60 bits +\end{lstlisting} + +Каждый selector получает виртуальный диапазон 260 bytes = 1 EiB. Назначения 0x0..0x7 зафиксированы архитектурой; 0x8..0xF остаются резервом. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + SEG Имя Назначение Guest dereference + + 0x0 DATA static/global objects R/W + + 0x1 STACK call/automatic storage R/W + + 0x2 PROGRAM machine code R only + + 0x3 CONTROL run control / configuration R only + + 0x4 INPUT environment -> model R only + + 0x5 OUTPUT model -> environment R/W + + 0x6 TRACE current telemetry frame R/W + + 0x7 HEAP runtime dynamic storage R/W, low guard excluded + + 0x8..0xF reserved future spaces POINTER_FAULT +\end{lstlisting} + +PTR64 = (SEG << 60) | OFFSET + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + SEG(ptr) = (ptr >> 60) & 0xF +\end{lstlisting} + +OFFSET(ptr) = ptr \& 0x0FFFFFFFFFFFFFFF + +NULL остаётся единственным all-zero value. 0x0000000000000000 = PTR64(DATA,0). Назначение новых selectors не меняет NULL semantics. DATA[0..15] является NULL guard; HEAP[0..15] является отдельным heap guard, но PTR64(HEAP,0) не является NULL. + +ADDR64 и PTR64 - не одно и то же. ADDR64 в прямых LOAD/STORE остаётся raw DATA offset. PTR64 является runtime value с selector+offset и используется косвенными memory/ control-flow operations. + +\section{Создание указателей: LEA, LEASP, LEAFP} +\subsection{LEA: полный PTR64 immediate} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LEA r64@dst, symbol_or_expression +\end{lstlisting} + +machine: OPCODE16 + REG24 + PTR64IMM = 13 bytes runtime: r64@dst = decoded PTR64 immediate + +LEA сохраняет прежний размер 13 bytes, но его 64-bit machine field теперь нормативно трактуется как полный PTR64 immediate, а не как DATA-only ADDR64. Ассемблер определяет selector по section, к которой принадлежит label: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + Label section LEA result + + .data PTR64(DATA, offset) + + .control PTR64(CONTROL, offset) + + .input PTR64(INPUT, offset) + + .output PTR64(OUTPUT, offset) + + .trace PTR64(TRACE, offset) + + .init / .text / .fini PTR64(PROGRAM, offset) + + .input +\end{lstlisting} + +force: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .real 128, 0.0 + + .text +\end{lstlisting} + +\_life: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LEA r64@0x00100, force ; PTR64(INPUT, offset(force)) + LOADP r128@0x00200, r64@0x00100 +\end{lstlisting} + +Чистое absolute/.equ expression без section label сохраняет историческую DATA semantics. Для DATA-label binary encoding остаётся совместимым с прежним LEA: selector DATA равен нулю, поэтому старший nibble 64-bit field не меняет bytes. + +\subsection{LEASP и LEAFP} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LEASP r64@dst, signed_offset -> PTR64(STACK, SP + signed_offset) + LEAFP r64@dst, signed_offset -> PTR64(STACK, FP + signed_offset) +\end{lstlisting} + +Эти instructions остаются специальными быстрыми constructors для automatic stack objects. Формирование one-past pointer допустимо при offset, равном architectural segment size; dereference всё равно требует, чтобы полный transfer range был valid. + +\section{Косвенная адресация: LOADP и STOREP} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LOADP R/C, r64(PTR64) ; memory[ptr] -> register + STOREP r64(PTR64), R/C ; register -> memory[ptr] +\end{lstlisting} + +machine size = OPCODE16 + REG24 + REG24 = 8 bytes + +Pointer operand всегда является physical r64. Размер transfer задаётся шириной value-register; bytes копируются без endian conversion. + +\begin{center} +\begin{tabularx}{0.98\textwidth}{@{}L{25mm}L{24mm}L{24mm}Y@{}} +\toprule +\textbf{Сегмент} & \textbf{LOADP} & \textbf{STOREP} & \textbf{Причина политики доступа}\\ +\midrule +DATA & да & да & обычная изменяемая память\\ +STACK & да & да & автоматические объекты и кадры вызовов\\ +PROGRAM & да & нет & код доступен для чтения, запись запрещена\\ +CONTROL & да & нет & конфигурация принадлежит runtime/supervisor\\ +INPUT & да & нет & входные воздействия принадлежат среде\\ +OUTPUT & да & да & модель формирует выходные данные\\ +TRACE & да & да & модель формирует текущий telemetry frame\\ +HEAP & да & да & динамическая guest-память; offsets 0..15 защищены\\ +0x8..0xF & POINTER\_FAULT & POINTER\_FAULT & селектор не назначен\\ +\bottomrule +\end{tabularx} +\end{center} + +CPU не хранит type tag у PTR64. Pointer provenance, pointee type, object lifetime, ownership и масштабирование на sizeof(T) являются ответственностью compiler/runtime ABI. CONTROL и INPUT read-only именно для guest CPU; host runtime API имеет право обновлять их в разрешённые lifecycle points. + +\section{Pointer arithmetic} +Pointer arithmetic выполняется только над 60-битным BYTE OFFSET. Четырёхбитный SEG никогда не участвует в переносе или заёме и всегда сохраняется без изменения. Единица смещения - один байт. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,x=0.22cm,y=0.85cm,>=Latex] + \draw[fill=McpuGreenBg] (0,0) rectangle (4,1); + \draw[fill=McpuBlueBg] (4,0) rectangle (32,1); + \node at (2,0.5) {SEG}; + \node at (18,0.5) {BYTE OFFSET (60 bits)}; + \node[above] at (2,1) {4 bits}; + \draw[->,very thick,McpuGreen] (2,-0.25) -- (2,-1.05) node[below,align=center]{копируется\\без изменения}; + \draw[<->,very thick,McpuBlue] (8,-0.55) -- (28,-0.55) node[midway,below=2pt]{\(\pm\) signed byte delta}; + \node[below=13mm,text=McpuUrl,align=center] at (24,0) {перенос/заём за bit 59\\$\rightarrow$ POINTER\_FAULT}; +\end{tikzpicture} +\caption{PADD/PSUB изменяют только OFFSET; SEG остаётся неизменным.} +\end{figure} + +\subsection{Набор инструкций} +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{27mm}Y L{22mm}@{}} +\toprule +\textbf{Мнемоника} & \textbf{Семантика} & \textbf{Размер}\\ +\midrule +PADD d,p,n & \code{d = p + signed64(n)} & 11 bytes\\ +PADDI d,p,k & \code{d = p + signed OFF64(k)} & 16 bytes\\ +PSUB d,p,n & \code{d = p - signed64(n)} & 11 bytes\\ +PSUBI d,p,k & \code{d = p - signed OFF64(k)} & 16 bytes\\ +PDIFF d,a,b & \code{d = OFFSET(a) - OFFSET(b)}, signed byte count & 11 bytes\\ +\bottomrule +\end{tabularx} +\end{center} + +Во всех register-формах операнды обязаны быть физическими r64. Для PADD/PSUB третий r64 трактуется как знаковое 64-битное число в дополнительном коде. Для PADDI/PSUBI смещение кодируется существующим OFF64 в canonical MSB-first byte order. Реализация использует фиксированные типы LibMPU \_\_mpu\_int64\_t и \_\_mpu\_uint64\_t, выбранные configure с гарантированной шириной 64 бита. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + PADD r64@dst, r64@ptr, r64@delta + PADDI r64@dst, r64@ptr, 24 + PSUB r64@dst, r64@ptr, r64@delta + PSUBI r64@dst, r64@ptr, -16 + PDIFF r64@bytes, r64@p2, r64@p1 +\end{lstlisting} + +\subsection{Границы и fault} +Результирующий offset обязан лежать в диапазоне 0 .. 0x0fffffffffffffff. Попытка выйти ниже нуля или выше 60-битного предела вызывает CPU\_EXEC\_POINTER\_FAULT. Destination при fault не изменяется. PSW pointer arithmetic не изменяет. + +Важно: PADD/PSUB не выполняют dereference и поэтому не сравнивают offset с фактически выделенным размером DATA/STACK/PROGRAM. Они проверяют только архитектурную 60-битную границу PTR64. Фактические границы проверяются позднее, при выполнении \code{LOADP/STOREP}. Это позволяет формировать one-past и промежуточные указатели; их языковая допустимость остаётся задачей compiler. + +\subsection{PDIFF} +PDIFF разрешён только если segment selectors обоих PTR64 совпадают. Разность двух 60-битных offsets всегда помещается в signed 64 bits и возвращается как число байтов. Разные SEG дают CPU\_EXEC\_POINTER\_FAULT. Само значение SEG может быть и зарезервированным: арифметика его только сравнивает/сохраняет и не пытается dereference. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; conceptual C lowering + p + n -> PADDI/PADD with n * sizeof(*p) bytes + p - n -> PSUBI/PSUB with n * sizeof(*p) bytes + p2 - p1 -> PDIFF byte_delta; compiler scales by sizeof(*p) +\end{lstlisting} + +ISA сознательно не содержит операции pointer + pointer. Типовая арифметика, масштабирование элементов, provenance и правила сравнения принадлежат high-level compiler. + +\section{FP как специальное архитектурное состояние} + +\term{FP}{frame pointer, указатель кадра} --- третий специальный 64-битный координатный регистр CPU рядом с PC и SP. Он не занимает байты register file и не является PTR64. В контексте CPU FP хранит raw byte offset внутри STACK. + +\begin{center} +\begin{tabularx}{0.9\textwidth}{@{}L{22mm}L{36mm}Y@{}} +\toprule +\textbf{Состояние} & \textbf{Физический смысл} & \textbf{Назначение}\\ +\midrule +PC & 64-bit offset в PROGRAM & адрес следующей machine instruction\\ +SP & 64-bit offset в STACK & подвижная граница живой stack storage\\ +FP & 64-bit offset в STACK & стабильная база текущего stack frame\\ +PSW & 32-bit status/control word & FLAGS, traps/interrupt mode и status bits\\ +\bottomrule +\end{tabularx} +\end{center} + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,node distance=8mm] + \node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,minimum width=31mm,minimum height=10mm] (pc) {PC}; + \node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,right=of pc,minimum width=31mm,minimum height=10mm] (sp) {SP}; + \node[draw=McpuBlue,rounded corners,fill=McpuGreenBg,right=of sp,minimum width=31mm,minimum height=10mm] (fp) {FP}; + \node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,right=of fp,minimum width=31mm,minimum height=10mm] (psw) {PSW}; + \node[below=3mm of pc,color=McpuBlue2] {PROGRAM offset}; + \node[below=3mm of sp,color=McpuBlue2] {STACK offset}; + \node[below=3mm of fp,color=McpuBlue2] {STACK offset}; + \node[below=3mm of psw,color=McpuBlue2] {status/control}; + \node[draw=McpuBlue2,fill=McpuCode,below=15mm of $(sp)!0.5!(fp)$,minimum width=108mm,minimum height=12mm] (rf) {512 KiB перекрывающегося register file}; + \draw[McpuGreen,dashed,thick] ($(fp.south)+(0,-2mm)$) -- ($(fp.south)+(0,-8mm)$); + \node[below=2mm of rf,color=McpuGreen,align=center] {FP не является \code{r64@...} и не занимает ни одного байта register file}; +\end{tikzpicture} +\caption{FP относится к специальному состоянию CPU и не перекрывается с register file.} +\end{figure} + +При создании STACK начальное состояние равно \code{SP = FP = stack_size}. FP является optional: leaf/simple function может вообще не устанавливать frame pointer. Compiler включает FP, когда нужна стабильная база для fixed locals, VLA/runtime-sized objects, spills, сложных frames или удобной диагностики. + +\section{Кадр стека (stack frame): пролог и эпилог} + +Сразу после обычного CALL layout определяется относительно входного SP: + +\begin{center} +\begin{tabularx}{0.78\textwidth}{@{}L{34mm}Y@{}} +\toprule +\textbf{Адрес} & \textbf{Содержимое}\\ +\midrule +\code{SP + 0} & адрес возврата (return PC)\\ +\code{SP + 8} & начало блок аргументов (argument block)\\ +\code{SP + 8 + k} & argument slot с block offset \code{k}\\ +\bottomrule +\end{tabularx} +\end{center} + +Функция, использующая FP, выполняет канонический prologue: + +\begin{lstlisting} +PUSHFP +SETFP +ADJSP -fixed_frame_size +\end{lstlisting} + +\code{PUSHFP} сохраняет старый FP как 64-bit value и уменьшает SP на 8. \code{SETFP} фиксирует текущий SP как базу нового frame. После этого fixed locals получают стабильные отрицательные offsets от FP, а arguments --- стабильные положительные offsets. + +\begin{center} +\begin{tabularx}{0.88\textwidth}{@{}L{27mm}L{34mm}Y@{}} +\toprule +\textbf{Offset от FP} & \textbf{Объект} & \textbf{Комментарий}\\ +\midrule +\code{FP + 0} & сохранённый FP caller-а & значение, записанное PUSHFP\\ +\code{FP + 8} & адрес возврата (return PC) & значение, записанное CALL\\ +\code{FP + 16} & блок аргументов (argument block) +0 & начало блока аргументов caller-а\\ +\code{FP + 16 + k} & argument slot & аргумент с block offset \code{k}\\ +\code{FP - ...} & fixed locals/spills & offsets назначает compiler/frame layout\\ +\bottomrule +\end{tabularx} +\end{center} + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,x=1cm,y=1cm] + \def\xL{3.0}\def\xR{8.5} + \fill[McpuGreenBg] (\xL,5.0) rectangle (\xR,6.0); + \fill[McpuGrayBg] (\xL,4.0) rectangle (\xR,5.0); + \fill[yellow!11] (\xL,3.1) rectangle (\xR,4.0); + \fill[yellow!11] (\xL,2.2) rectangle (\xR,3.1); + \fill[McpuGrayBg] (\xL,1.2) rectangle (\xR,2.2); + \draw[McpuBlue,thick] (\xL,1.2) rectangle (\xR,6.0); + \foreach \y in {2.2,3.1,4.0,5.0} {\draw[McpuBlue2] (\xL,\y)--(\xR,\y);} + \node at (5.75,5.5) {динамические объекты / VLA}; + \node at (5.75,4.5) {фиксированные локальные объекты / spills}; + \node at (5.75,3.55) {сохранённый FP caller-а}; + \node at (5.75,2.65) {адрес возврата (return PC)}; + \node at (5.75,1.7) {блок аргументов (argument block)}; + \draw[McpuGreen,very thick] (2.55,4.0)--(8.9,4.0); + \node[anchor=east,color=McpuGreen] at (2.4,4.0) {FP}; + \draw[McpuBlue,very thick] (2.55,5.88)--(8.9,5.88); + \node[anchor=east,color=McpuBlue] at (2.4,5.88) {SP}; + \node[anchor=west,color=McpuBlue2] at (8.8,3.55) {FP + 0}; + \node[anchor=west,color=McpuBlue2] at (8.8,2.65) {FP + 8}; + \node[anchor=west,color=McpuBlue2] at (8.8,1.7) {FP + 16 + ...}; + \node[anchor=west,color=McpuBlue2] at (0.4,6.15) {малые STACK offsets}; + \node[anchor=west,color=McpuBlue2] at (0.4,1.0) {большие STACK offsets}; + \draw[->,McpuBlue,very thick] (9.8,5.45)--(9.8,4.65); + \node[anchor=west,color=McpuBlue] at (10.0,5.05) {SP может двигаться}; +\end{tikzpicture} +\caption{Frame с FP: fixed objects имеют стабильные offsets, а SP может двигаться ниже FP.} +\end{figure} + +\subsection{Инструкции управления кадром (frame instructions)} + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{20mm}L{25mm}Y@{}} +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Семантика}\\ +\midrule +0x5030 & \code{PUSHFP} & \code{SP -= 8; STACK[SP] = FP}\\ +0x5031 & \code{SETFP} & \code{FP = SP}\\ +0x5032 & \code{RESTSP} & \code{SP = FP}\\ +0x5033 & \code{POPFP} & \code{FP = STACK[SP]; SP += 8}\\ +\bottomrule +\end{tabularx} +\end{center} + +Канонический epilogue: + +\begin{lstlisting} +RESTSP ; discard dynamic and fixed frame storage below FP +POPFP ; restore caller FP; SP now points to адрес возврата (return PC) +RET ; restore PC +\end{lstlisting} + +CALL/RET сами FP не меняют. Сохранение и восстановление frame pointer является частью prologue/epilogue только тех функций, которые решили использовать FP. Leaf/simple functions могут работать только через SP. + +\begin{principle} +Главное назначение FP --- отделить стабильную геометрию frame от временных движений SP. Fixed local всегда остаётся по тому же \code{FP-offset}, даже если функция создаёт outgoing блок аргументов (argument block)s или runtime-sized temporary storage. +\end{principle} + +\section{Адресация относительно FP (FP-relative addressing)} +Три инструкции являются прямыми аналогами SP-relative операций, но используют стабильную базу FP: + +\begin{center} +\begin{tabularx}{0.97\textwidth}{@{}L{20mm}L{38mm}L{42mm}Y@{}} +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Машинный формат} & \textbf{Семантика}\\ +\midrule +0x5040 & LDFP R, OFF64 & 2 + 3 + 8 = 13 bytes & \code{R = STACK[FP + off]}\\ +0x5041 & STFP OFF64, R & 2 + 8 + 3 = 13 bytes & \code{STACK[FP + off] = R}\\ +0x5042 & LEAFP r64, OFF64 & 2 + 3 + 8 = 13 bytes & \code{r64 = PTR64(STACK, FP + off)}\\ +\bottomrule +\end{tabularx} +\end{center} + +OFF64 - signed 64-bit byte displacement в canonical MSB-first encoding. Для LDFP/STFP CPU проверяет, что полный range объекта помещается в физически allocated STACK. LEAFP проверяет вычисленный offset и разрешает pointer formation на границе stack\_size (one-past), но не выполняет dereference. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; fixed local scalar at FP-8 + STFP -8, r64@0x00100 + LDFP r64@0x00108, -8 + + ; pointer to that local object + LEAFP r64@0x00110, -8 + LOADP r64@0x00118, r64@0x00110 +\end{lstlisting} + +\subsection{Смещения блока аргументов после установки FP} +После PUSHFP; SETFP начало блок аргументов (argument block) всегда имеет фиксированный адрес FP+16. Но offsets individual arguments вычисляются внутри блока по их ABI alignment. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + FP + 0 сохранённый FP caller-а + FP + 8 адрес возврата (return PC) + FP + 16 блок аргументов (argument block) base + FP + 16 + k argument at block offset k + ... +\end{lstlisting} + +Без aggregate return первый explicit argument имеет block offset 0. При aggregate return hidden result PTR64 занимает block offset 0, а первый explicit argument начинается с align\_up(8, alignof(arg1)). Поэтому, например, для 8-byte aligned arg1 это FP+24, а для 16-byte aligned arg1 - FP+32. + +Compiler invariant: между SETFP и соответствующим POPFP FP рассматривается compiler-ом как стабильная база. PUSH/POP/CALL/ADJSP/SETSP не меняют FP. + +\section{Динамическое выделение памяти в STACK и SETSP} +Dynamic objects не требуют отдельной ALLOCA-инструкции. LibMCPU использует уже существующие PTR64 + pointer arithmetic и одну операцию установки SP: + +\begin{center} +\begin{tabularx}{0.92\textwidth}{@{}L{20mm}L{34mm}L{28mm}Y@{}} +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Машинный формат} & \textbf{Семантика}\\ +\midrule +0x5050 & SETSP r64(PTR64) & 2 + 3 = 5 bytes & если SEG=STACK и offset входит в allocated STACK, \code{SP = OFFSET(ptr)}\\ +\bottomrule +\end{tabularx} +\end{center} + +SETSP принимает только физический r64 с PTR64. DATA/PROGRAM/reserved segment дают CPU\_EXEC\_POINTER\_FAULT; STACK offset больше фактического stack\_size даёт CPU\_EXEC\_STACK\_FAULT. При fault SP не изменяется. + +\subsection{Выделение N байт во время выполнения} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; r64@0x00118 contains runtime byte count N + LEASP r64@0x00110, 0 ; mark = PTR64(STACK, текущий SP) + PSUB r64@0x00120, r64@0x00110, r64@0x00118 + SETSP r64@0x00120 ; reserve N bytes downward + LEASP r64@0x00130, 0 ; &object == new stack top + + ; ... use object through r64@0x00130 ... + + SETSP r64@0x00110 ; release this dynamic scope +\end{lstlisting} + +Pointer arithmetic работает в байтах. Масштабирование n * sizeof(T) и alignment выполняет compiler. CPU не добавляет hidden padding. + +\subsection{Понижение VLA (VLA-style lowering)} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; VLA/dynamic object lowering. + ; Assume compiler has already computed the required aligned byte count + ; into r64@0x00118 at runtime. +\end{lstlisting} + +f: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + PUSHFP + SETFP + ADJSP -16 ; fixed part of frame, if needed + + LEASP r64@0x00110, 0 ; save stack mark before dynamic object + PSUB r64@0x00120, r64@0x00110, r64@0x00118 + SETSP r64@0x00120 + LEASP r64@0x00130, 0 ; PTR64 to first byte of dynamic object + + ; ... access object through r64@0x00130 ... + + RESTSP ; releases dynamic and fixed frame storage + POPFP + RET +\end{lstlisting} + +Размер dynamic object. CPU получает число байтов уже вычисленным в обычном r64. Вычисление n * sizeof(T), overflow checks и align-up относятся к compiler lowering и могут использовать доступные integer operations. Механизм FP/SETSP от конкретного способа вычисления размера не зависит. + +\subsection{Вложенные динамические области} + +Runtime-sized objects удобно освобождать не по размеру, а восстановлением заранее сохранённой stack mark: + +\begin{lstlisting} +LEASP r64@0x00200, 0 ; внешняя отметка +; allocate object A +... +LEASP r64@0x00208, 0 ; внутренняя отметка +; allocate object B +... +SETSP r64@0x00208 ; release B-scope +... +SETSP r64@0x00200 ; release A-scope +\end{lstlisting} + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,x=1cm,y=1cm] + \def\xL{3.0}\def\xR{8.8} + \fill[McpuGreenBg] (\xL,5.1) rectangle (\xR,6.0); + \fill[McpuGreenBg] (\xL,4.15) rectangle (\xR,5.1); + \fill[McpuGrayBg] (\xL,3.15) rectangle (\xR,4.15); + \fill[yellow!11] (\xL,2.25) rectangle (\xR,3.15); + \fill[yellow!11] (\xL,1.35) rectangle (\xR,2.25); + \draw[McpuBlue,thick] (\xL,1.35) rectangle (\xR,6.0); + \foreach \y in {2.25,3.15,4.15,5.1} {\draw[McpuBlue2] (\xL,\y)--(\xR,\y);} + \node at (5.9,5.55) {внутренняя динамическая область B}; + \node at (5.9,4.62) {динамическая область A}; + \node at (5.9,3.65) {фиксированная часть frame}; + \node[color=McpuBlue2] at (5.9,3.37) {адресуется относительно FP}; + \node at (5.9,2.70) {saved FP}; + \node at (5.9,1.80) {адрес возврата (return PC)}; + \draw[McpuBlue,very thick] (2.55,5.85)--(9.15,5.85); + \node[anchor=east,color=McpuBlue] at (2.4,5.85) {текущий SP}; + \draw[McpuGreen,thick] (2.7,5.1)--(9.0,5.1); + \node[anchor=west,color=McpuGreen] at (9.2,5.1) {внутренняя отметка}; + \draw[McpuGreen,thick] (2.7,4.15)--(9.0,4.15); + \node[anchor=west,color=McpuGreen] at (9.2,4.15) {внешняя отметка}; + \draw[McpuBlue2,very thick] (2.7,3.15)--(9.0,3.15); + \node[anchor=west,color=McpuBlue2] at (9.2,3.15) {FP}; +\end{tikzpicture} +\caption{Dynamic scopes меняют SP, но FP и fixed frame остаются стабильными.} +\end{figure} + +\begin{normative} +Когда frame установлен, compiler не должен использовать SETSP для выхода выше FP и не должен захватывать caller-owned bytes. CPU аппаратно проверяет границы всего allocated STACK; принадлежность конкретных bytes текущему frame контролирует compiler/ABI. +\end{normative} + +\section{C-подобная модель автоматических объектов (automatic objects)} + +STACK является полноценной памятью automatic storage duration. Локальные структуры, union, массивы, VLA и runtime-sized temporaries живут в stack frame. Scalar locals могут оставаться в register file либо spill-иться в STACK. + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{37mm}L{58mm}Y@{}} +\toprule +\textbf{Объект} & \textbf{Рекомендуемая база} & \textbf{Время жизни}\\ +\midrule +fixed scalar spill & FP + fixed negative offset & до RESTSP / возврата\\ +fixed struct/array & FP + fixed negative offset & до RESTSP / возврата\\ +VLA/runtime object & PTR64 от LEASP после SETSP & до восстановления соответствующей stack mark / RESTSP\\ +argument & FP + positive offset; без FP --- SP-relative & в пределах call\\ +\bottomrule +\end{tabularx} +\end{center} + +PTR64 на automatic object имеет обычный C-like lifetime: после освобождения dynamic scope или frame он становится семантически недействительным, даже если underlying STACK bytes физически ещё существуют. + +Alignment не является скрытой функцией CPU. Для ABI-conforming frame/argument layout используется максимум 16 bytes natural alignment и 16-byte CALL alignment; offsets и padding явно создаются assembler/compiler. + +\section{Формальный ABI аргументов} +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{55mm}Y@{}} +\toprule +\textbf{Категория} & \textbf{ABI representation}\\ +\midrule +Integer scalar & by value в блок аргументов (argument block)\\ +Real scalar & by value в блок аргументов (argument block)\\ +Complex scalar & by value в блок аргументов (argument block)\\ +PTR64 & 8-byte scalar by value\\ +struct/union by value & полное object representation в блок аргументов (argument block)\\ +struct/union by pointer & PTR64 на исходный object\\ +array parameter в C-like semantics & PTR64 на первый element\\ +\bottomrule +\end{tabularx} +\end{center} + +Argument block base имеет alignment 16. Каждый slot получает ABI alignment своего значения; полный размер блока округляется к 16. Padding является частью call layout, но не частью значения scalar argument. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; one r64 argument + ADJSP -16 + STSP 0, r64@0x00000 + CALL f + ADJSP 16 +\end{lstlisting} + +На входе callee блок аргументов (argument block) начинается по SP+8. Если callee выполняет PUSHFP; SETFP, тот же block начинается по FP+16. First explicit argument без hidden result имеет block offset 0; при hidden result его offset вычисляется по обычному align-up rule и не обязан быть фиксированным. + +Почему это важно. Если hidden PTR64 занимает block offset 0, следующий argument с alignment 8 начнётся с +8, а argument с alignment 16 - с +16. Поэтому ABI не кодирует постоянную формулу «первый explicit argument всегда hidden+8». + +\section{Структуры и массивы} +\subsection{Fixed local struct} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + PUSHFP + SETFP + ADJSP -16 + + STFP -16, r64@0x00100 + STFP -8, r64@0x00108 + LEAFP r64@0x00110, -16 ; &local_struct + ... +\end{lstlisting} + +\subsection{Structure by value} +Caller создаёт exact copy complete object representation в argument area. Для memory-to-memory переноса используется BMOV/BMOVI, поэтому aggregate не обязан проходить через register file и его размер не ограничен 8192 bytes максимального register view. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; struct S s occupies 48 bytes in caller frame + LEAFP r64@0x00100, -96 ; src = &s + ADJSP -48 ; 48-byte блок аргументов (argument block), already 16-aligned + LEASP r64@0x00108, 0 ; dst = argument slot +0 + BMOVI r64@0x00108, r64@0x00100, 48 + CALL f + ADJSP 48 ; caller cleanup +\end{lstlisting} + +\subsection{Local array / VLA} +Массив фиксированного размера получает постоянное отрицательное смещение от FP. VLA получает динамическую область STACK через LEASP/PSUB/SETSP; compiler сохраняет PTR64 на начало массива в регистре или spill-слоте, если текущий SP затем будет двигаться дальше. + +\subsection{Array argument} +В обычной C-семантике array parameter передаётся как PTR64 на первый element. Копирование всего массива не происходит. + +\section{Aggregate return: hidden result pointer} +Scalar integer/real/complex/pointer возвращаются через Return Value Area. Structure/union произвольного размера использует caller-provided storage и hidden PTR64. + +Hidden result is argument slot 0. Caller помещает PTR64 destination в block offset 0, затем layout explicit arguments начинается после него с обычным align-up. В callee без FP block base находится по SP+8; после PUSHFP; SETFP - по FP+16. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; Example: hidden result PTR64 + aggregate argument aligned to 8 + ; block offset +0 hidden result PTR64 + ; block offset +8 explicit aggregate + ; block is rounded up to 16-byte total size + + ; callee with FP + PUSHFP + SETFP + LDFP r64@0x00100, 16 ; hidden result = block +0 + ; explicit offsets are determined by ABI layout, not by a universal constant + ... + RESTSP + POPFP + RET +\end{lstlisting} + +Callee записывает aggregate bytes через hidden PTR64. RET не копирует aggregate и не трогает Return Value Area для такого результата. Caller освобождает весь блок аргументов (argument block) одним ADJSP. + +\section{Block copy: BMOV и BMOVI} +Block-copy переносит произвольное число байтов непосредственно между архитектурными memory spaces через PTR64. Register file используется только для самих указателей и, в форме BMOV, для runtime byte count. Содержимое копируемого объекта не проходит через REG24. + +\begin{center} +\begin{tabularx}{0.97\textwidth}{@{}L{20mm}L{45mm}Y L{18mm}@{}} +\toprule +\textbf{Код операции} & \textbf{Мнемоника} & \textbf{Машинный формат} & \textbf{Размер}\\ +\midrule +0x0150 & BMOV dst, src, count & OPCODE16 + REG24 + REG24 + REG24 & 11 bytes\\ +0x0151 & BMOVI dst, src, IMM64 & OPCODE16 + REG24 + REG24 + IMM64 & 16 bytes\\ +\bottomrule +\end{tabularx} +\end{center} + +Все register operands этих двух инструкций обязаны быть физическими r64. Первые два r64 содержат PTR64 destination и source. Третий r64 у BMOV интерпретируется как unsigned 64-bit byte count (\_\_mpu\_uint64\_t). У BMOVI byte count находится непосредственно в PROGRAM как unsigned IMM64, MSB-first. + +Семантика всегда memmove. Если source и destination ranges перекрываются в одном сегменте, результат обязан быть таким же, как у C memmove(). Направление физического копирования не является частью ISA и выбирается реализацией. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + BMOVI r64@dst_ptr, r64@src_ptr, 64 + ; copies exactly 64 bytes + + LDI r64@size, 4096 + BMOV r64@dst_ptr, r64@src_ptr, r64@size + ; runtime-sized 4096-byte copy +\end{lstlisting} + +\subsection{Разрешённые memory spaces} +\begin{center} +\begin{tabularx}{0.82\textwidth}{@{}L{38mm}L{35mm}Y@{}} +\toprule +\textbf{Segment} & \textbf{Source} & \textbf{Destination}\\ +\midrule +DATA (0x0) & да & да\\ +STACK (0x1) & да & да\\ +PROGRAM (0x2) & да & нет\\ +CONTROL (0x3) & да & нет\\ +INPUT (0x4) & да & нет\\ +OUTPUT (0x5) & да & да\\ +TRACE (0x6) & да & да\\ +HEAP (0x7) & да, кроме low guard & да, кроме low guard\\ +0x8..0xF & POINTER_FAULT & POINTER_FAULT\\ +\bottomrule +\end{tabularx} +\end{center} + +BMOV/BMOVI используют те же segment access rules, что LOADP/STOREP. CONTROL и INPUT могут быть sources, но не destinations. OUTPUT/TRACE/HEAP являются обычными writable data-object spaces. PROGRAM остаётся read-only source. + +\subsection{Проверка ranges и atomicity} +До первого изменения destination CPU обязан: + +1. прочитать оба PTR64 и byte count; 2. проверить source segment и полный range [src, src+count); 3. проверить destination segment, write permission и полный range [dst, dst+count); 4. только после успешных проверок выполнить block move. + +Правило атомарной ошибки: если диапазон source или destination недопустим, байты destination не изменяются. BMOV/BMOVI не меняют PSW, SP, FP и сами PTR64-регистры. + +PTR64 offset может быть one-past физического сегмента только при нулевом размере. count == 0 является корректным no-op после проверки segment/access policy; запись в PROGRAM остаётся запрещённой даже для destination PROGRAM. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,x=1cm,y=1cm] + \node[anchor=west,color=McpuBlue2] at (0.2,2.7) {До \code{BMOVI dst=src+8, count=16}:}; + \foreach \i/\lab in {0/A,1/B,2/C,3/D} { + \draw[McpuBlue] (1.0+1.5*\i,1.55) rectangle ++(1.5,0.72); + \node at (1.75+1.5*\i,1.91) {\lab}; + } + \draw[->,McpuGreen,thick] (1.0,1.3)--(1.0,0.92) node[below,color=McpuGreen]{source}; + \draw[->,McpuBlue,thick] (2.5,1.3)--(2.5,0.92) node[below,color=McpuBlue]{destination}; + \node[anchor=west,color=McpuBlue2] at (0.2,0.10) {После memmove-semantics:}; + \foreach \i/\lab in {0/A,1/A,2/B,3/D} { + \draw[McpuBlue] (6.8+1.5*\i,0.0) rectangle ++(1.5,0.72); + \node at (7.55+1.5*\i,0.36) {\lab}; + } +\end{tikzpicture} +\caption{BMOV/BMOVI определены как memmove, поэтому overlapping ranges обрабатываются корректно.} +\end{figure} + +\subsection{Lowering: assignment структуры} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; C-like: dst = src; sizeof(struct S) = 64 + LEAFP r64@0x00100, -64 ; &dst + LEAFP r64@0x00108, -128 ; &src + BMOVI r64@0x00100, r64@0x00108, 64 +\end{lstlisting} + +CPU копирует complete object representation, включая padding bytes. Понимание полей структуры остаётся задачей compiler. + +\subsection{Lowering: aggregate argument by value} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; source aggregate S lives at FP-96, sizeof(S)=48, align <=16 + LEAFP r64@0x00100, -96 + ADJSP -48 ; complete 16-aligned блок аргументов (argument block) + LEASP r64@0x00108, 0 + BMOVI r64@0x00108, r64@0x00100, 48 + CALL consume_S + ADJSP 48 +\end{lstlisting} + +Для нескольких arguments assembler/compiler вычисляет offsets внутри цельного блок аргументов (argument block) в declaration order, соблюдая alignment каждого slot; общий размер округляется к 16. + +\subsection{Lowering: aggregate return через hidden PTR64} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; callee with FP; hidden result PTR64 at FP+16 + LDFP r64@0x00100, 16 ; destination supplied by caller + LEAFP r64@0x00108, -128 ; local result object + BMOVI r64@0x00100, r64@0x00108, 128 + RESTSP + POPFP + RET +\end{lstlisting} + +RET по-прежнему не знает о структуре. BMOVI просто переносит готовый object representation в caller-provided storage. + +\subsection{Runtime-sized arrays и VLA} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; dst_ptr and src_ptr are PTR64; r64@0x00110 = runtime byte count + BMOV r64@0x00100, r64@0x00108, r64@0x00110 +\end{lstlisting} + +Compiler предварительно вычисляет element\_count * sizeof(T), выполняет требуемые overflow checks и передаёт итоговый byte count. BMOV не знает element type и не выполняет масштабирование. + +\subsection{memcpy и memmove в C-like frontend} +Одна архитектурная операция достаточна для обеих high-level семантик. Вызов memmove естественно lowering-ится в BMOV/BMOVI. memcpy также может lowering-иться в ту же инструкцию: memmove-semantics являются более сильной гарантией и не меняют результат программы при неперекрывающихся ranges. Frontend всё равно может диагностировать нарушение своих собственных правил aliasing/overlap. + +\section{ABI data layout и assembler alignment} +\subsection{Natural alignment} +Базовое правило LibMCPU ABI для обычного scalar/object storage: + +natural\_alignment = min(sizeof(object), 16) + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ABI_MAX_ALIGN = 16 + STACK/CALL_ALIGN = 16 +\end{lstlisting} + +Это означает, например: r8 -> align 1, r32 -> align 4, r64/PTR64 -> align 8, r128 и все более крупные numeric objects -> align 16. Размер объекта при этом не меняется: real4096 занимает 512 bytes, но ABI-natural alignment равен 16, а не 512. + +\subsection{Assembler не вставляет hidden alignment} +.integer, .real и .complex записывают bytes непосредственно в текущую DATA position. Только явный .align N меняет position. Благодаря этому один и тот же assembler поддерживает normal и packed layout. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; normal ABI-like layout + .data + .align 16 +\end{lstlisting} + +obj: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 8, 1 ; offset +0 + .align 4 + .integer 32, 2 ; next 4-byte aligned field + .align 2 + .integer 16, 3 + .align 4 ; trailing struct padding + + ; packed binary layout: no implicit padding +\end{lstlisting} + +packed\_obj: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 8, 1 + .integer 32, 2 + .integer 16, 3 +\end{lstlisting} + +Explicit .align может быть больше 16 (например 64, 512 или 4096) для специальных таблиц, cache/page oriented data или внешнего формата. Такое требование относится к guest offset и не увеличивает ABI natural alignment типа. + +\subsection{Normal и packed aggregate layout} +Для normal aggregate поля размещаются с их natural alignment, alignment aggregate равен максимуму alignment полей (не более 16), а sizeof округляется вверх до aggregate alignment. Packed aggregate имеет alignment 1, не имеет automatic internal/trailing padding и сохраняет внутренний layout вложенного normal aggregate как самостоятельного типа. + +Bit-fields. LibMCPU ABI не требует отдельного bit-field layout: необходимые операции представляются обычными integer masks/shifts. Это сохраняет binary layout явным и переносимым. + diff --git a/doc/src/libmcpu-architecture-runtime-ru.tex b/doc/src/libmcpu-architecture-runtime-ru.tex new file mode 100644 index 0000000..2ecf4cb --- /dev/null +++ b/doc/src/libmcpu-architecture-runtime-ru.tex @@ -0,0 +1,349 @@ +\section{Host backing memory spaces и loader} +Host allocation не является частью PTR64. Активные architectural memory spaces имеют независимый anonymous mmap() backing; guest pointer содержит selector+offset и никогда не содержит host virtual address. + +\subsection{Anonymous mmap only} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + mmap(NULL, capacity, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0) +\end{lstlisting} + +PROGRAM, DATA, STACK, CONTROL, INPUT, OUTPUT, TRACE и HEAP получают отдельные mappings. Loader читает image bytes обычным read() в anonymous memory. PROGRAM после загрузки переводится host-side в read-only mapping; guest access policy дополнительно проверяется instruction resolver. CONTROL/INPUT сохраняются host-writable, потому что runtime должен иметь возможность менять configuration/input между фазами исполнения, но guest STOREP в них запрещён. + +\subsection{Architectural size, capacity и HEAP limit} +\begin{center} +\begin{tabularx}{0.92\textwidth}{@{}L{34mm}Y@{}} +\toprule +\textbf{Величина} & \textbf{Смысл}\\ +\midrule +\code{segment.size} & точное число байтов, доступных guest-программе\\ +\code{segment.capacity} & округлённый по страницам размер host mapping\\ +\code{heap.size} & текущий guest-visible размер HEAP\\ +\code{heap.capacity} & текущая вместимость host backing HEAP\\ +\code{heap.limit} & настроенный неизменяемый максимальный размер HEAP\\ +\bottomrule +\end{tabularx} +\end{center} + +Все CPU bounds checks используют architectural size, а не host capacity. HEAP additionally reserves offsets 0..15; первый ordinary heap byte имеет offset CPU\_HEAP\_BEGIN = 16. + +Relocatable host backing. PTR64(HEAP,offset) не зависит от host base. Поэтому будущая реализация heap growth может создать larger anonymous mapping, скопировать bytes и заменить host base, не меняя ни одного guest pointer. + +\subsection{MELF loader и окончательный process image} +Начиная с MELF ABI, development sidecar images не являются форматом запуска. \code{mcpu-ld} создаёт ET_EXEC/ET_DYN с segment metadata, а \code{mcpu-run} валидирует target description, program-model metadata, program headers и relocation records до передачи управления guest. Для ABI v2 все preload-модули сначала полностью читаются и валидируются, затем назначаются независимые PROGRAM/DATA bases, после чего применяются retained relocations. PROGRAM после relocation phase переводится в read-only host mapping. + +\section{Fault, atomicity и invariants} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + • REG24 проверяет width, natural alignment и границы register file. + • JMPP/CALLP требуют PROGRAM PTR64: NULL, другой SEG или offset вне program.size дают + POINTER_FAULT до изменения PC/SP/STACK. + • CALLP сначала валидирует target и stack space; return PC записывается только после + успешной полной проверки. + • All-zero PTR64 является NULL и даёт POINTER_FAULT при indirect dereference. + • Assembler резервирует DATA[0..15]; ordinary DATA labels начинаются не раньше 0x10. + • CODE64 target должен ссылаться внутрь PROGRAM. + • STACK operations сначала проверяют bounds, затем меняют SP/FP/memory. + • PUSHFP проверяет место и корректность текущего FP до записи. + • POPFP сначала читает saved value и проверяет saved_fp <= stack_size; при fault ни FP, + ни SP не меняются. + • SETFP требует allocated STACK и корректный SP. + • RESTSP требует FP <= stack_size. + • LDFP/STFP/LEAFP используют signed OFF64 относительно FP и выполняют bounds + checking. + • SETSP принимает только STACK PTR64; wrong segment -> pointer fault, out-of-range offset + -> stack fault. + • Ни одна FP/SP management instruction не меняет PSW. + • CALL/CALLP/RET сами PSW-neutral; normal-function ABI считает PSW[15:0] volatile, TF/IF/ + MODE persistent control state, HF отдельным execution state. + • PADD/PADDI/PSUB/PSUBI проверяют 60-bit PTR64 offset, но не object lifetime. + + + + • BMOV/BMOVI сначала проверяют complete source и destination ranges; при fault destination + memory не изменяется. Overlap разрешён и имеет memmove semantics. + • CPU не обеспечивает ownership текущего frame: правило SP <= FP внутри FP-frame и + stack-scope discipline обеспечиваются compiler/ABI. +\end{lstlisting} + +Нельзя хранить долгоживущий указатель на automatic object после release scope/frame. PTR64 остаётся битово корректным, но high-level program нарушает lifetime. Это семантическая ошибка compiler/program, а не PTR64 encoding fault. + +\section{Byte order и architectural object representation} +LibMCPU намеренно разделяет два независимых byte-order domain. Это один из фундаментальных ABI contracts: PROGRAM является переносимым сериализованным instruction stream, а register file и memory segments содержат runtime objects в target representation, совместимом с LibMPU. + +\begin{figure}[ht] +\centering +\begin{tikzpicture}[font=\sffamily\small,>=Latex,node distance=12mm and 15mm] + \node[draw=McpuBlue,very thick,rounded corners,fill=McpuBlueBg,minimum width=72mm,minimum height=22mm,align=center] (prog) + {\textbf{PROGRAM / machine fields}\\[-1mm]\scriptsize OPCODE16, REG24, IMM64, ADDR64, CODE64, OFF64\\\textbf{канонический MSB-first}}; + \node[draw=McpuGreen,very thick,rounded corners,fill=McpuGreenBg,right=of prog,minimum width=72mm,minimum height=22mm,align=center] (data) + {\textbf{Архитектурные объекты данных}\\[-1mm]\scriptsize register file, DATA, STACK, CONTROL, INPUT, OUTPUT, TRACE, HEAP\\\textbf{target representation LibMPU}}; + \draw[->,thick,McpuBlue2] (prog) -- node[above]{decode / execute} (data); + \node[below=11mm of prog,align=center,font=\ttfamily\small] (pb) {0x1122334455667788\\11 22 33 44 55 66 77 88}; + \node[below=11mm of data,align=center,font=\ttfamily\small] (db) {тот же u64 на текущем LE target\\88 77 66 55 44 33 22 11}; + \draw[->,McpuBlue] (prog.south) -- (pb.north); + \draw[->,McpuGreen] (data.south) -- (db.north); +\end{tikzpicture} +\caption{Сериализация PROGRAM и целевое представление объектов данных являются разными byte-order domains.} +\end{figure} + +\subsection{PROGRAM: canonical MSB-first} +Все multi-byte machine fields внутри PROGRAM кодируются старшим байтом вперёд. Это правило не зависит от host CPU, LibMPU byte order или будущего object-file container. + +value = 0x1122334455667788 + +IMM64 / CODE64 / ADDR64 / OFF64 bytes: 11 22 33 44 55 66 77 88 + +REG24 также является canonical machine field: его три байта формируются из WIDTH и ADDRESS по уже определённому битовому layout. Decoder не делает C-cast на packed host structure. + +\subsection{Data-object domain} +Register file, DATA, STACK, CONTROL, INPUT, OUTPUT, TRACE и HEAP используют одно target object representation. Для integer-like objects byte order берётся из target profile LibMCPU, который совпадает с configured LibMPU: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + CPU_DATA_BYTE_ORDER = MPU_BYTE_ORDER + CPU_DATA_WORD_ORDER = MPU_WORD_ORDER + CPU_DATA_BYTE_ORDER_* = corresponding LibMPU settings + CPU_DATA_WORD_ORDER_* = corresponding LibMPU settings +\end{lstlisting} + +На текущей little-endian конфигурации: + +u64 value 0x1122334455667788 address+0 88 address+1 77 address+2 66 address+3 55 address+4 44 address+5 33 address+6 22 address+7 11 + +Общее representation. Если один object скопирован между register file или любыми data-object spaces без числового преобразования, его bytes не переставляются. Endian conversion не является скрытым side effect memory instructions. + +\subsection{Явные helper families в реализации} +Чтобы source code executor/assembler визуально не смешивал два domain, используются две группы accessors: + +\_\_cpu\_code\_get\_u16 / \_\_cpu\_code\_put\_u16 \_\_cpu\_code\_get\_u32 / \_\_cpu\_code\_put\_u32 \_\_cpu\_code\_get\_u64 / \_\_cpu\_code\_put\_u64 + +\_\_cpu\_data\_get\_u16 / \_\_cpu\_data\_put\_u16 \_\_cpu\_data\_get\_u32 / \_\_cpu\_data\_put\_u32 \_\_cpu\_data\_get\_u64 / \_\_cpu\_data\_put\_u64 + +code-helpers явно собирают/разбирают MSB-first fields. data-helpers работают с target object representation через byte-safe copy semantics. Слово data здесь означает architectural data-object domain, а не только PTR64 segment DATA=0x0; эти helpers применимы также к register-file и STACK bytes. + +Именно поэтому выбрано имя \_\_cpu\_data\_get\_u*, а не \_\_cpu\_object\_get\_u*: в исходниках сразу виден контраст code vs data. Во избежание путаницы с segment DATA это значение термина закреплено в ABI documentation. + +\subsection{Integer, PTR64, return PC и overlapping registers} +Signed integer использует two's-complement; unsigned - binary modulo 2\textasciicircum{}N. Их физический byte/word order является target data profile. PTR64 логически остаётся 4-bit SEG + 60-bit OFFSET, но когда он находится в r64, DATA field, STACK argument или structure member, это обычный 64-bit data object. + +PTR64(PROGRAM, 0x1234) = 0x2000000000001234 + +current LE target bytes: 34 12 00 00 00 00 00 20 + +Return PC, который CALL/CALLP помещают в STACK, тоже является 64-bit STACK data object. В самой CALL instruction CODE64 target encoded MSB-first; pushed return PC хранится в target data representation. RET читает его как data object и устанавливает логический PC. + +Overlapping register views физически наблюдают одни и те же bytes. Поэтому endian-sensitive assembler code, который разбирает широкое integer value через меньшие overlapping views, является target-specific low-level code. Это допустимо и сознательно является частью ABI. + +\subsection{Real и Complex} +LibMCPU не вводит отдельный формат floating/complex objects поверх LibMPU. Для real<bits> применяется external object representation соответствующего LibMPU real type при текущем target profile. Для complex<bits> используется LibMPU complex representation двух real components половинной ширины. + +Complex physical half order не фиксируется независимым правилом LibMCPU. Он следует configured LibMPU word-order profile. Нельзя считать универсальной истиной, что первые bytes complex object всегда являются real component, а вторые - imaginary component. Complex arithmetic должен использовать C-opcodes/LibMPU representation либо явно target-specific byte code. + +\subsection{Assembler data directives} +Directive inside .data создаёт target data object либо явно заданную raw byte sequence, а не canonical machine field: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data + .align 8 +\end{lstlisting} + +x: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 64, 0x1122334455667788 +\end{lstlisting} + +text16: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .ucs2 "Андрей" +\end{lstlisting} + +text8: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .utf8 "Андрей" +\end{lstlisting} + +raw: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .byte 0x11, 0x22, 0x33 + + .align 8 +\end{lstlisting} + +jump: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .cptr target + + .text +\end{lstlisting} + +target: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + HALT +\end{lstlisting} + +На текущем LE target bytes объекта x идут как 88 77 66 55 44 33 22 11. Но literal того же значения в LDI находится в PROGRAM как canonical 11 22 33 44 55 66 77 88. .integer/.int, .real и .complex генерируют target/LibMPU object representation. .ucs2 пишет target-representation 16-bit code units, тогда как .utf8 и .byte являются raw byte sequences без endian conversion. .cptr генерирует target-representation PTR64(PROGRAM,label/ expression). + +.cptr является каноническим mnemonic directive. Исторический .codeptr принимается assembler как compatibility alias. .label не используется: запись name: уже определяет label, тогда как .cptr создаёт объект-указатель. Полные правила строк, .equ и expressions приведены в разделе 34. + +\subsection{Byte-preserving memory operations} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LOAD / STORE + LOADP / STOREP + PUSH / POP + LDSP / STSP + LDFP / STFP + BMOV / BMOVI +\end{lstlisting} + +Эти операции не знают endian semantics конкретного объекта. Они переносят physical bytes. В частности: + +DATA source --LOAD--> register A DATA source --BMOV--> DATA/STACK destination --LOAD--> register B + +bytes(register A) == bytes(register B) + +Точно так же structure/array/packed object может быть скопирован BMOV без type metadata и без byte swap. Padding bytes, если они являются частью complete object representation, тоже сохраняются. + +\subsection{Raw bytes, packed I/O и external byte order} +char, byte arrays, UTF-8 и arbitrary binary blobs являются последовательностями байтов и сами по себе endian не имеют. packed означает отсутствие layout padding, но не означает network/ big-endian или little-endian protocol representation. + +Если внешний файл/протокол требует конкретный byte order, assembler/program должен выполнить явное преобразование. Автоматическая endian conversion при LOAD/STORE запрещена этой ABI-моделью. Для явного разворота физического порядка байтов LibMCPU предоставляет универсальную инструкцию BSWAP, подробно определённую в разделе 32. Она не подменяет понятие external byte order: решение о необходимости swap принимает программа или инструмент. + +\subsection{Target metadata в MELF} +MELF уже содержит нормативную target metadata: byte order/container encoding, machine id и MCPU target description. Loader не пытается эвристически переконвертировать DATA/STACK-compatible objects при несовместимом profile. Образ либо совместим с текущей LibMPU target representation, либо отвергается до исполнения. PROGRAM machine fields при этом сохраняют отдельное canonical MSB-first кодирование. + +\subsection{Host alignment и unaligned accesses} +Byte order не меняет ранее принятые alignment rules. Guest memory остаётся byte-addressable; unaligned accesses разрешены ISA. Реализация не должна читать произвольную guest memory через alignment-sensitive или strict-aliasing-unsafe casts. \_\_cpu\_data\_get\_u*/put используют + +byte-safe copy semantics, а большие arbitrary objects перемещаются memcpy/memmove или LibMPU routines. + +\section{Pointer comparison, red zone, active/reserved} +PTR64 и BSWAP Этот раздел фиксирует четыре низкоуровневых соглашения CPU/assembler ABI, необходимые до появления object/executable format: сравнение PTR64, отсутствие stack red zone, поведение активных/зарезервированных segment selectors и универсальное преобразование порядка физических байтов. + +\subsection{Equality и ordering для PTR64} +PTR64 остаётся обычным 64-битным архитектурным значением. Для равенства/неравенства сравниваются все 64 бита: selector и 60-bit offset. Поэтому существующего integer compare достаточно: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ICMP r64@p, r64@q + JZ equal + JNZ not_equal +\end{lstlisting} + +Это правило корректно и для разных selector: DATA:0x100 и STACK:0x100 не равны, поскольку их полные PTR64 различаются. Обычный ICMP может также выдавать signed integer flags для двух pointer bit patterns, однако ABI не придаёт этому результату смысла pointer ordering. + +Pointer ordering определяется только внутри одного segment selector и выполняется через PDIFF. Если selectors различны, PDIFF возвращает CPU\_EXEC\_POINTER\_FAULT и destination остаётся неизменным. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + PDIFF r64@delta, r64@p, r64@q ; signed byte difference + ITEST r64@delta, r64@delta + JS p_less_than_q + JZ p_equal_q +\end{lstlisting} + +Разность двух 60-bit offsets всегда помещается в signed 64-bit диапазон. CPU не знает границ high-level объектов/массивов: same-segment ordering является низкоуровневой адресной операцией, а object-domain restrictions будущего языка остаются задачей compiler/program. + +\subsection{Stack red zone отсутствует} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + CPU_ABI_STACK_RED_ZONE_SIZE = 0 +\end{lstlisting} + +STACK растёт вниз. Если текущий SP = X, байты ниже X не считаются принадлежащими функции как скрытая scratch-area. Функция должна сначала явно изменить SP через ADJSP или валидированный SETSP, а уже затем использовать выделенную область. + +Важно: ISA может физически позволить SP-relative address с отрицательным OFF64, если он попадает в allocated STACK. Это не превращает область ниже текущего SP в ABI red zone. Использование невыделенных bytes является ABI violation, а не обязательным аппаратным fault. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; неправильно как normal ABI scratch + STSP -8, r64@tmp + + ; правильно + ADJSP -16 + STSP 0, r64@tmp + ... + ADJSP 16 +\end{lstlisting} + +Отсутствие red zone упрощает будущие traps/interrupts: обработчик может строить собственный frame ниже текущего SP, не уничтожая скрытые temporaries leaf-функции. + +\subsection{Active и reserved PTR64 selectors} +Selectors 0x0..0x7 имеют назначенную address-space semantics. Selectors 0x8..0xF остаются структурно допустимыми PTR64 values, но не имеют memory semantics. Это сохраняет возможность будущего расширения без изменения PTR64 encoding. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + Операция над reserved PTR64 Правило +\end{lstlisting} + +(0x8..0xF) + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + MOV / SWAP / byte storage разрешено: bit pattern можно копировать и хранить + + ICMP equality / inequality разрешено как full 64-bit comparison + + PADD/PADDI/PSUB/PSUBI разрешено; selector сохраняется, arithmetic только по 60-bit + offset + + PDIFF разрешено только при одинаковом selector + + LOADP / STOREP / BMOV / BMOVI CPU_EXEC_POINTER_FAULT + + JMPP / CALLP CPU_EXEC_POINTER_FAULT + + SETSP CPU_EXEC_POINTER_FAULT +\end{lstlisting} + +HEAP теперь является активным selector 0x7. Его low guard 0..15 недоступен для dereference, но это не делает HEAP:0 значением NULL. All-zero DATA:0 остаётся единственным NULL PTR64. + +\subsection{BSWAP: raw byte reversal} +BSWAP имеет opcode 0x0103 и 8-byte machine form: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + BSWAP r<bits>@dst, r<bits>@src +\end{lstlisting} + +OPCODE16 + REG24(dst) + REG24(src) + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + 2 bytes + 3 bytes + 3 bytes = 8 bytes +\end{lstlisting} + +Оба operands обязаны иметь одинаковую физическую ширину. Ассемблер использует именно r<bits> syntax, подчёркивая raw/non-complex interpretation. Допустимы все существующие widths от r8 до r65536. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + source: b0 b1 b2 ... b(N-2) b(N-1) +\end{lstlisting} + +destination: b(N-1) b(N-2) ... b1 b0 + +r8 является identity. Для r65536 разворачиваются все 8192 bytes. BSWAP не знает, является ли содержимое integer, PTR64, real, complex или частью packed record: операция определена только над физическими байтами. + +PSW invariant. BSWAP не изменяет ни volatile status flags, ни TF/IF/MODE/HF. Width mismatch возвращает CPU\_EXEC\_WIDTH\_FAULT до изменения destination. + +\subsection{In-place и snapshot semantics} +Архитектурно BSWAP определяется как чтение полного source snapshot и только затем запись reversed bytes в destination. Поэтому in-place форма полностью определена: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + BSWAP r64@0x00100, r64@0x00100 +\end{lstlisting} + +При текущем natural alignment equal-width register views не могут частично перекрываться иначе, чем полностью совпасть. Тем не менее snapshot formulation является нормативной и не зависит от внутренних оптимизаций исполнителя. + +\subsection{External binary formats} +BSWAP не означает «to little endian» или «to big endian». Она лишь разворачивает bytes. Решение о swap принимает программа/tool, сравнивая external format с текущим CPU\_DATA\_BYTE\_ORDER. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + ; native target-order value + LDI r64@0x00000, 42 + + ; физически reversed representation (например wire order, если он противоположен target) + BSWAP r64@0x00008, r64@0x00000 + + ; обратное преобразование + BSWAP r64@0x00010, r64@0x00008 +\end{lstlisting} + +Для packed file/protocol records это даёт явное и предсказуемое преобразование. Packed layout и byte order остаются независимыми понятиями: packed убирает padding, BSWAP меняет порядок bytes конкретного field. + diff --git a/doc/src/libmcpu-assembler-details-ru.tex b/doc/src/libmcpu-assembler-details-ru.tex new file mode 100644 index 0000000..75a5c4a --- /dev/null +++ b/doc/src/libmcpu-assembler-details-ru.tex @@ -0,0 +1,233 @@ +\section{Ассемблер: UTF-8 source, строки, .equ и arithmetic expressions} +Низкоуровневый mcpu-as обязан уметь описывать binary images data-like sections, labels и relocation-like address expressions без обязательного текстового препроцессора. Текстовая метапрограмма (\#define, \#include, conditional compilation, parameterized macros) остаётся отдельным будущим слоем. В assembler входят только механизмы, непосредственно связанные с machine/data representation и разрешением символов. + +\subsection{Кодировка assembler source} +Исходный файл mcpu-as всегда UTF-8. Результат сборки не зависит от LANG, LC\_CTYPE или другой locale host-системы. Перед разбором assembler проверяет source как well-formed UTF-8 через LibMPUIO. + +Это особенно важно для строковых директив: одинаковый .s обязан создавать одинаковые image bytes на любых build hosts при одинаковом LibMCPU target profile. + +\subsection{Командная строка mcpu-as} +Ассемблер всегда создаёт один перемещаемый объект MELF ET\_REL, поэтому отдельный переключатель режима сборки не нужен. Выходной файл задаётся обычной UNIX-опцией \code{-o}, а единственный исходный файл является позиционным аргументом: + +\begin{lstlisting}[language={}] +mcpu-as -o file.o file.s +mcpu-as file.s -o file.o +\end{lstlisting} + +Обе формы полностью эквивалентны: порядок \code{-o} и входного файла свободный. Ровно один входной файл и ровно один выходной файл обязательны. Опции \code{-h} и \code{--help} печатают справку. Историческая форма \code{mcpu-as -c input.s output.o} удалена: \code{-c} больше не является опцией, поскольку другого режима работы у assembler нет. + +\subsection{.utf8 - NUL-terminated UTF-8 byte string} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data +\end{lstlisting} + +name\_utf8: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .utf8 "Андрей" +\end{lstlisting} + +.utf8 записывает UTF-8 payload буквально как bytes и добавляет один завершающий 0x00. Никакого target endian преобразования и никакой locale conversion нет. + +"Андрей" -> D0 90 D0 BD D0 B4 D1 80 D0 B5 D0 B9 00 + +Поддерживаются escapes \textbackslash{}\textbackslash{}, \textbackslash{}", \textbackslash{}n, \textbackslash{}r, \textbackslash{}t, \textbackslash{}b, \textbackslash{}f, \textbackslash{}v, \textbackslash{}a, byte escape \textbackslash{}xHH, а также Unicode scalar escapes \textbackslash{}uXXXX и \textbackslash{}UXXXXXXXX. Результирующая последовательность снова проверяется как UTF-8. + +Встроенный NUL внутри .utf8/.ucs2 намеренно не поддерживается: эти директивы определяют C-style NUL-terminated strings. Для произвольного binary layout с внутренними нулями используется .byte или .integer/.int. + +\subsection{.ucs2 - UTF-8 source literal в strict UCS-2 target objects} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .data +\end{lstlisting} + +name\_ucs2: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .ucs2 "Андрей" +\end{lstlisting} + +Assembler сначала получает UTF-8 literal, затем использует strict LibMPUIO UTF-8 -> UCS-2 conversion. Каждый UCS-2 code unit записывается как отдельный 16-bit architectural data object с помощью target data byte order; в конце добавляется U+0000. + +U+0410 U+043D U+0434 U+0440 U+0435 U+0439 U+0000 + +current little-endian target bytes: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + 10 04 3D 04 34 04 40 04 35 04 39 04 00 00 +\end{lstlisting} + +Strict UCS-2 допускает scalar values U+0000..U+FFFF, кроме surrogate range U+D800..U+DFFF. UTF-8 characters выше U+FFFF (например emoji) не раскладываются на UTF-16 surrogate pairs, а дают assembler error. Таким образом .ucs2 точно соответствует принятой в LibMCPU/LibMPUIO модели UCS-2, а не UTF-16. + +\subsection{.byte, .integer и .int} +.byte создаёт raw bytes без implicit terminator, alignment или endian conversion: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .byte 0x4c, 0x43, 0x50, 0x55 + .byte 1, 2+3, BLOCK_SIZE/512 +\end{lstlisting} + +Каждый элемент должен находиться в диапазоне 0..255. .integer сохраняет прежний смысл typed integer object, а .int является полным синонимом. Обе директивы принимают список значений одной ширины: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 16, 0x0410, 0x043d, 0x0000 + .int 8, 65, 66, 0 +\end{lstlisting} + +Каждое значение формирует отдельный object указанной width непосредственно в текущей data-like section position; hidden padding отсутствует. + +\subsection{.equ - абсолютные assembler symbols} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .equ table_ptr, 0x03000 + .equ block_size, 4 * 512 + .equ next_reg, table_ptr + 8 +\end{lstlisting} + +.equ section-independent и не создаёт bytes. Имена symbols case-sensitive. Все .equ definitions собираются до emission, поэтому разрешены forward references между .equ symbols. + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .equ является absolute constant symbol. В текущем low-level assembler выражение +\end{lstlisting} + +справа может ссылаться на integer literals и другие .equ, но не на DATA/CODE labels. Это сохраняет различие между absolute assembler symbols и section-relative labels/relocations MELF. + +Имя label не может совпадать с именем .equ. + +\subsection{Integer expression grammar и precedence} +primary := integer | symbol | '(' expression ')' + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + unary := ['+' | '-'] unary | primary +\end{lstlisting} + +product := unary \{ ('*' | '/' | '\%') unary \} expression := product \{ ('+' | '-') product \} + +Операции имеют обычный arithmetic precedence: unary, затем multiply/divide/remainder, затем add/subtract. Division by zero, overflow internal expression domain и значение вне требуемой width дают assembler error. + +Expressions используются для absolute numeric operands: register byte address, LDI, stack/pointer signed offsets, BMOVI count, .align, .byte, integer widths/values и других соответствующих immediate fields. + +\subsection{Label expressions и fixups} +Address expressions дополнительно получают labels тех sections, которые допустимы для конкретного operand class. Поэтому assembler непосредственно поддерживает: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LEA r64@table_ptr, input_vector+8 ; selector follows label section + LOAD r64@next_reg, data_object+16 ; direct LOAD remains DATA-relative + JMP loop+0 ; PROGRAM CODE64 + CALL function + .cptr handler+0 +\end{lstlisting} + +LEA принимает labels из DATA/CONTROL/INPUT/OUTPUT/TRACE и code sections и создаёт полный PTR64 selector+offset. Direct LOAD/STORE остаются в namespace DATA labels + .equ. CODE64 operands и .cptr используют PROGRAM labels + .equ. Forward labels работают через fixups. CPU по-прежнему не хранит instruction-boundary metadata: arithmetic CODE expression обязано использоваться программистом осмысленно. + +\subsection{Symbolic register addresses} +REG24 byte address может быть integer expression из absolute .equ symbols: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .equ table_ptr, 0x03000 + .equ temp_ptr, table_ptr + 8 + + LEA r64@table_ptr, table + LDI r64@temp_ptr, 42 + LDI r64@(table_ptr + 16), 7 +\end{lstlisting} + +После вычисления expression assembler выполняет обычную проверку register width, natural alignment и границ 512 KiB register file. DATA/CODE labels в register-address expression не участвуют: label address и register-file byte address являются разными namespaces. + +\subsection{Граница mcpu-as и будущего препроцессора} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + Механизм Уровень Причина + + .init/.text/.fini/.data/.control/.input/.output/.trace, mcpu-as определяют layout + labels, .align executable/data + images + + .integer/.int/.real/.complex/.byte/.utf8/.ucs2/.cptr mcpu-as создают data-like + objects/bytes + + + + + + .equ, arithmetic expressions, symbol fixups mcpu-as необходимы для + адресов и будущих + relocations + + #define/#undef/#include/#if/#ifdef отдельный текстовая + preprocessor metaprogramming не + является machine + encoding + + parameterized macros / repetition отдельный source transformation + preprocessor до mcpu-as +\end{lstlisting} + +Таким образом обычный symbolic assembler остаётся самодостаточным, а будущая схема .S -> cpu-cpp -> mcpu-as может добавляться независимо и не усложняет binary semantics cpu-as. + +\subsection{Полный пример} +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .equ table_ptr, 0x03000 + .equ next_reg, table_ptr + 8 + .equ block_size, 4 * 512 + + .data +\end{lstlisting} + +name16: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .ucs2 "Андрей" +\end{lstlisting} + +name8: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .utf8 "Андрей" +\end{lstlisting} + +header: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .byte 0x4c, 0x43, 0x50, 0x55, block_size/512 +\end{lstlisting} + +letters: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .int 16, 0x0410, 0x043d, 0x0434, 0 + .align 16 +\end{lstlisting} + +answer: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + .integer 64, 42 + + .text +\end{lstlisting} + +start: + +\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize] + LEA r64@table_ptr, name16 + LOAD r64@next_reg, answer + HALT +\end{lstlisting} + +Здесь ни один identifier не требует textual macro substitution: table\_ptr/next\_reg - absolute assembler symbols, name16/answer - DATA labels, а arithmetic expression вычисляется самим assembler. + + +\subsection{Маркеры строк препроцессора и логические координаты diagnostics} +После появления \code{mcpu-cpp} assembler не использует собственную директиву \code{\#line} для управления source coordinates. Вместо неё \code{mcpu-as} распознаёт маркеры строк, оставляемые препроцессором в выходном потоке: +\begin{lstlisting}[language={}] +# digit-sequence +# digit-sequence "filename" +# digit-sequence "filename" flags... +\end{lstlisting} +\code{digit-sequence} содержит как минимум одну decimal digit и всегда разбирается в основании 10, даже при leading zeroes. Допустимый диапазон - 0..2147483647. Значение задаёт logical line number для physical source line, непосредственно следующей за marker. Filename optional; при его отсутствии сохраняется предыдущий logical filename. Всё после закрывающей кавычки filename считается полем flags препроцессора и полностью игнорируется assembler. + +Marker не создаёт bytes, symbols, \code{__LINE__} или \code{__FILE__}, не участвует в expression evaluation и влияет только на source coordinates последующих diagnostics. Empty и comment-only ordinary lines увеличивают logical counter обычным образом. + +Строки \code{\#pragma} и \code{\#lang} предназначены для других участников toolchain: \code{mcpu-as} полностью игнорирует их и не увеличивает logical line counter. Любая другая строка, начинающаяся с \code{\#}, также удаляется из assembler token stream, но учитывается как одна ordinary logical source line. + +\begin{normative} +\textbf{Историческая форма \#line.} Запись \code{\#line N "file"} больше не является source-location directive \code{mcpu-as}. Она обрабатывается как обычная неизвестная строка \code{\#...}: игнорируется как assembler syntax и учитывается при продвижении logical counter. Координаты source задаются только preprocessing line marker формой \code{\# N "file" flags...}. Это diagnostics contract и не часть MELF ABI. +\end{normative} diff --git a/doc/src/libmcpu-assembler-directives-table.tex b/doc/src/libmcpu-assembler-directives-table.tex new file mode 100644 index 0000000..e7200b9 --- /dev/null +++ b/doc/src/libmcpu-assembler-directives-table.tex @@ -0,0 +1,37 @@ +\begin{longtable}{@{}L{35mm}L{48mm}L{76mm}@{}} +\toprule +\textbf{Директива} & \textbf{Форма} & \textbf{Назначение}\\ +\midrule +\endfirsthead +\toprule +\textbf{Директива} & \textbf{Форма} & \textbf{Назначение}\\ +\midrule +\endhead +\code{.init} & без аргументов & Переключает текущую code section на INIT. Для final low-level ET_EXEC здесь определяется \code{_start}; ET_DYN такую секцию иметь не может.\\ +\code{.text} & без аргументов & Основная PROGRAM code section. Здесь находится \code{_life}, а PROGRAM без отдельного INIT имеет \code{_start == _life}.\\ +\code{.fini} & без аргументов & FINI code section; low-level entry symbol --- \code{_fini}. HLL finalization создаётся CRT автоматически.\\ +\code{.data} & без аргументов & Инициализированные writable DATA objects.\\ +\code{.bss} & без аргументов & Zero-initialized DATA, сериализуется как NOBITS.\\ +\code{.control} & без аргументов & Model CONTROL space contribution; guest read-only во время исполнения. Только main ET_EXEC.\\ +\code{.input} & без аргументов & Model INPUT space contribution. Только main ET_EXEC.\\ +\code{.output} & без аргументов & Model OUTPUT space contribution. Только main ET_EXEC.\\ +\code{.trace} & без аргументов & Model TRACE space contribution. Только main ET_EXEC.\\ +\code{.align} & \code{.align N} & Выравнивает следующую позицию текущей section до степени/границы, проверяемой assembler-ом; добавляет padding либо увеличивает BSS size.\\ +\code{.zero} & \code{.zero N} & Резервирует N нулевых байтов; особенно естественна в \code{.bss}.\\ +\code{.byte} & \code{.byte expr[, ...]} & Записывает raw bytes 0..255 без terminator, alignment и endian conversion.\\ +\code{.integer}, \code{.int} & \code{.int bits, expr[, ...]} & Создаёт целые target objects заданной ширины. \code{.int} --- полный синоним \code{.integer}.\\ +\code{.real} & \code{.real bits, value} & Создаёт target Real object указанной physical width через LibMPU representation.\\ +\code{.complex} & \code{.complex bits, real, imag} & Создаёт target Complex object; \code{bits} задаёт physical width всего Complex object.\\ +\code{.utf8} & \code{.utf8 "text"} & Записывает проверенную UTF-8 NUL-terminated byte string. Embedded NUL не допускается.\\ +\code{.ucs2} & \code{.ucs2 "text"} & Преобразует UTF-8 source literal в strict UCS-2 target code units и добавляет U+0000. Surrogate range и scalar values выше U+FFFF запрещены.\\ +\code{.cptr}, \code{.codeptr} & \code{.cptr code-expr} & Создаёт PROGRAM/CODE pointer object с relocation при необходимости. \code{.codeptr} --- синоним.\\ +\code{.ptr} & \code{.ptr pointer-expr} & Создаёт полный PTR64 на допустимый target segment/label с relocation при необходимости.\\ +\code{.equ} & \code{.equ name, expr} & Определяет absolute assembler symbol, не создающий bytes. Допускает forward references между \code{.equ}; section labels в таком выражении не используются.\\ +\code{.global}, \code{.globl} & \code{.global name} & Делает symbol GLOBAL. \code{.globl} --- синоним.\\ +\code{.extern} & \code{.extern name} & Объявляет внешний undefined symbol для последующей компоновки.\\ +\code{.weak} & \code{.weak name} & Объявляет/помечает symbol как WEAK согласно MELF symbol binding.\\ +\bottomrule +\end{longtable} + +\paragraph{Особые синтаксические формы.} +\code{_start_life:} --- assembler pseudo-label, эквивалентный последовательным \code{_start:} и \code{_life:} по одному адресу в \code{.text}. Он не является директивой и не создаёт отдельный MELF symbol \code{_start_life}. Preprocessor line markers \code{\# N "file" flags...} меняют только logical coordinates diagnostics; \code{\#pragma} и \code{\#lang} игнорируются assembler-ом как строки, принадлежащие другим стадиям toolchain. diff --git a/doc/src/libmcpu-bytecode-architecture-ru.tex b/doc/src/libmcpu-bytecode-architecture-ru.tex new file mode 100644 index 0000000..cbf9268 --- /dev/null +++ b/doc/src/libmcpu-bytecode-architecture-ru.tex @@ -0,0 +1,230 @@ +\documentclass[10pt,a4paper,oneside,openany]{memoir} +\newcommand{\DocShortTitle}{LibMCPU: архитектура машинного кода и ABI} +\input{libmcpu-doc-common.tex} +\hypersetup{pdftitle={LibMCPU: архитектура машинного кода, памяти и ABI вызовов},pdfsubject={Технический справочник по архитектуре LibMCPU}} +\begin{document} +\DocTitlePage{LibMCPU: архитектура машинного кода,\\памяти и ABI вызовов}{Регистровая модель, ISA, PTR64, CALL ABI, lifecycle и runtime}{Техническое справочное руководство} + +\ManualIntro{Документ фиксирует текущий архитектурный контракт LibMCPU: register model, ISA, memory spaces, PTR64, CALL ABI, MELF lifecycle, syscall/error ABI, dynamic startup linking и подписанный HLL Runtime ABI между mcpu-ld, CRT и MCPU Libc. Документ намеренно не связан с номером выпуска пакета. Для бинарного формата MELF действует отдельное руководство \code{libmcpu-melf-abi-ru.pdf}.} + +\begin{principle} +\textbf{Главный принцип.} Физический регистр LibMCPU нетипизирован. \REG{} задаёт только физическую ширину и байтовый адрес в едином register file. Integer/Real/Complex смысл определяется opcode; запись \code{c<bits>@addr} является semantic view того же физического регистра. Архитектурный \PTR{} также является обычным 64-битным значением, но его декодирование и права доступа задаются ABI. +\end{principle} + +\tableofcontents +\clearpage + +\part{Базовая архитектура, ISA и CALL ABI} +\input{libmcpu-architecture-core-ru.tex} + +\part{Memory backing, представление объектов и assembler} +\input{libmcpu-architecture-runtime-ru.tex} +\input{libmcpu-isa-coverage-ru.tex} +\input{libmcpu-assembler-details-ru.tex} + +\part{Program Model ABI, runtime и динамическая загрузка} +\section{Program Model Lifecycle ABI} +Каждый исполняемый MELF-файл имеет обязательные lifecycle symbols \code{_start} и \code{_life}; \code{e_entry} всегда указывает на \code{_start}. Секция \code{.init} и символ \code{_fini} необязательны. Эти точки входа принадлежат MELF/runtime ABI и не должны смешиваться с функциями будущего языка высокого уровня. + +\subsection{С отдельным INIT} +\begin{lstlisting} +.init +_start: + ... one-time startup ... + END + +.text +_life: + ... resumable LIFE phase ... + END +\end{lstlisting} +Тогда \code{e_init_entry=_start}, а \code{e_life_entry=_life}. Первый \code{END} завершает INIT; runtime устанавливает PC в \code{_life} и начинает LIFE. Последующие LIFE \code{END} возвращают управление supervisor-у, а следующий resume продолжается с инструкции после соответствующего \code{END}. + +\subsection{Без INIT} +\begin{lstlisting} +.text +_start_life: + ... startup / ordinary program trampoline ... +\end{lstlisting} +Псевдометка \code{_start_life:} эквивалентна двум последовательным labels \code{_start:} и \code{_life:} по одному адресу. В final executable \code{e_init_entry=0} и \code{e_entry == e_life_entry}. Эта форма особенно естественна для обычной PROGRAM, которая заканчивается через \code{EXIT} и не создаёт LIFE boundaries. + +\subsection{FINI, END и EXIT} +\code{END} завершает текущую управляемую фазу и возвращает управление supervisor-у. Graceful \code{EXIT(status)} завершает normal execution; если имеется \code{.fini}/\code{_fini}, runtime сохраняет status, входит в FINI и требует завершить FINI инструкцией \code{END}. Второй \code{EXIT} внутри FINI не является нормальным контрактом. Diagnostic stop по \code{--steps} не симулирует graceful \code{EXIT} и потому сам по себе FINI не запускает. + +\input{libmcpu-hll-crt-contract-ru.tex} + +\section{McpuStartup и argc/argv} +Перед первой guest-инструкцией загрузчик создаёт постоянную область запуска (startup area) в верхней части STACK. Заголовок имеет размер 32 байта и выравнивание 16 байт: +\par\medskip +\noindent\begin{tabularx}{\textwidth}{@{}L{20mm}L{30mm}L{24mm}Y@{}} +\toprule +\textbf{Смещение} & \textbf{Поле} & \textbf{Размер} & \textbf{Значение}\\ +\midrule +0x00 & version & 4 & \code{MCPU_STARTUP_VERSION_1}\\ +0x04 & size & 4 & 32\\ +0x08 & argc & 8 & число аргументов guest-программы\\ +0x10 & argv & 8 & \PTR(STACK, массив argv)\\ +0x18 & reserved & 8 & ноль\\ +\bottomrule +\end{tabularx} +После заголовка расположен массив \code{argv[0..argc]} из PTR64, завершающий NULL, затем NUL-terminated строки аргументов. Эта область остаётся действительной всё время жизни executable. Начальное состояние: \code{PC=e_entry}, \code{SP=startup_base}, \code{FP=stack_size}. + +\section{HEAP и program break} +HEAP --- отдельный runtime-сегмент SEG=0x7. Он не расширяет DATA и не имеет assembler payload. Определяемый компоновщиком неподменяемый символ \code{_end} равен \PTR(HEAP,16); это начальный и минимальный program break. + +Runtime хранит \code{_heap_brk}, \code{_heap_size}, \code{_heap_capacity}, immutable \code{_heap_limit}. Запрос ниже 16 или выше limit отклоняется и возвращает прежний break. Shrink не обязан уменьшать host mapping/capacity; освобождённый range немедленно zeroes. Regrow в уже существующий backing снова открывает deterministic zero memory. При backing relocation guest pointers остаются valid, потому что содержат segment+offset. + +\section{SYSCALL ABI и SYS_brk} +Zero-operand instruction \code{SYSCALL} использует fixed overlapping register area в верхней части register file. +\par\medskip +\noindent\begin{tabularx}{\textwidth}{@{}L{32mm}L{32mm}Y@{}} +\toprule +\textbf{Role} & \textbf{Address/view} & \textbf{Meaning}\\ +\midrule +SYSNO & \code{r32@0x7fffc} & syscall number\\ +NARGS & \code{r32@0x7fff8} & argument count\\ +RET & \code{r64@0x7fff8} & return value; overlaps NARGS\\ +ARG0..ARG5 & \code{r64@0x7fff0} ... \code{r64@0x7ffc8} & up to six arguments\\ +\bottomrule +\end{tabularx} + +Первый syscall --- \code{SYS_brk}, number 10. Он требует один argument: NULL означает query, HEAP PTR64 requests absolute new break. Общее правило syscall ABI использует стабильные host-independent MCPU errno: неуспех возвращает отрицательное \code{-MCPU_errno}, а host errno преобразуется на sysdeps boundary и наружу не выходит. \code{SYS_brk} является специальным исключением: он возвращает фактический/current HEAP PTR64 и при accepted, и при rejected request. Unsupported syscall number возвращает \code{-ENOSYS} из MCPU errno namespace. + +\begin{lstlisting} +; query current break +MOV r32@0x7fff8, 1 +LDI r32@0x7fffc, 10 +CLR r64@0x7fff0 +SYSCALL +; result: r64@0x7fff8 +\end{lstlisting} + +\section{Startup dynamic linking: архитектурный взгляд} +Текущий normal dynamic contract --- frozen MELF ABI v3. \code{mcpu-ld --dynamic} создаёт ABI-v3 ET_EXEC, а \code{mcpu-ld --shared --soname=NAME} --- ABI-v3 ET_DYN. В отличие от исторического ABI v2, normal dependencies записываются в canonical non-ALLOC \code{.dynamic} как \code{DT_NEEDED}, а shared object имеет обязательный \code{DT_SONAME}. ABI v2 остаётся читаемым как historical explicit-preload input format. + +\code{mcpu-run} обнаруживает NEEDED graph до исполнения guest-кода, проверяет SONAME identity, загружает dependencies breadth-first/left-to-right и завершает symbol resolution/RELA до первой инструкции \code{_start}. Явный \code{--ldpreload} и \code{MCPU_LD_PRELOAD} остаются override/interposition mechanisms. PLT/GOT, lazy binding, \code{dlopen()}, \code{dlsym()} и runtime lookup после старта отсутствуют. + +ET_DYN содержит только загружаемое PROGRAM/DATA содержимое \code{.text/.data/.bss} плюс non-ALLOC metadata; lifecycle, CONTROL/INPUT/OUTPUT/TRACE, STACK и HEAP принадлежат главному executable. + +\subsection{GLOBAL/WEAK interposition} +Сильный GLOBAL имеет приоритет над WEAK. Для equal-strength definitions highest scope имеет explicit preload с установленным rightmost priority, затем main executable, затем normal NEEDED graph в breadth-first/left-to-right order. Lifecycle symbols \code{_start/_life/_fini} остаются частными для executable и не участвуют в interposition. + +\subsection{LOCAL ET_DYN runtime rebasing} +\begin{normative} +LOCAL symbol остаётся private и non-preemptible, но ссылка на него внутри ET_DYN \textbf{не может быть окончательно превращена в process address при static link}: final PROGRAM/DATA base shared object ещё неизвестна. Поэтому такая ссылка сохраняется как LOCAL runtime RELA. Startup linker разрешает её строго против originating module и добавляет runtime segment base. LOCAL symbols не попадают в GLOBAL/WEAK winner namespace. +\end{normative} + +Правило возникло как исправление runtime-address contract ещё в ABI v2 и является нормативной частью frozen ABI v3. Оно не открывает LOCAL symbol для interposition. + +\section{mcpu-run как host-side test bench} +Помимо one-shot inspection options, runtime поддерживает четыре lifecycle stages: +\par\medskip +\noindent\begin{tabularx}{\textwidth}{@{}L{36mm}Y@{}} +\toprule +\textbf{Опция} & \textbf{Граница выполнения}\\ +\midrule +\code{--before-run=<action>} & after load/preload/relocations/startup, before first guest instruction\\ +\code{--after-init=<action>} & after INIT END; при отсутствии .init --- один раз на logical empty INIT boundary\\ +\code{--on-step=<action>} & after every LIFE END\\ +\code{--after-run=<action>} & after final normal stop; graceful FINI уже завершён\\ +\bottomrule +\end{tabularx} + +Stage-опции можно повторять; они выполняются слева направо. Фильтры по номеру шага намеренно отсутствуют. + +\subsection{Typed actions} +\begin{lstlisting}[language={}] +print-int-regs:r64@0x100,r64@0x108 +print-real-regs:r128@0x200,r256@0x300 +set-int-regs:r64@0x100=123,r64@0x108=-17 +set-real-regs:r128@0x200=1.25,r128@0x220=-2.5 + +print-int-<memory>:64@16,64@24 +print-real-<memory>:128@32,128@48 +set-int-<memory>:64@16=123,64@24=456 +set-real-<memory>:128@32=1.25,128@48=2.5 +\end{lstlisting} +\code{<memory>} --- data, control, heap, input, output, trace. Один scalar print-action печатает comma-list одной строкой через spaces. Set values разбираются через LibMPUIO \code{mpu_sscanf()}; whole action валидируется до первой записи, а guest PSW сохраняется вокруг host-side numeric parsing. + +\subsection{Raw actions} +\code{print-raw-<memory>:<bytes>@<addr>} read-only и описывает ровно один dump. Comma-list запрещён; для нескольких dumps повторяется stage option. \code{set-raw-*} не существует. Size принимает raw bytes и K/KiB, M/MiB, G/GiB с binary multipliers. DATA raw view ограничен linked \code{.data} и не перелезает в \code{.bss}; HEAP host view ограничен prepared backing, тогда как guest access ограничен current break. + +\subsection{Двойной дефис как разделитель} +Preferred CLI: +\begin{lstlisting}[language={}] +mcpu-run [runtime-options] -- executable [program-args...] +\end{lstlisting} +После separator \lstinline|--| tokens не интерпретируются как runner options. Первый token становится executable spelling и guest \code{argv[0]}. Legacy form без separator сохранена. + + +\section{Бинарные утилиты toolchain} +Системные инструменты MELF используют единый public format contract и не вводят альтернативного представления машинного кода. +\par\medskip +\noindent\begin{tabularx}{\textwidth}{@{}L{36mm}Y@{}} +\toprule +\textbf{Команда} & \textbf{Назначение}\\ +\midrule +\code{mcpu-readelf} & Структурный read-only просмотр MELF headers, sections, symbols, RELA, dynamic и MCPU metadata.\\ +\code{mcpu-ar} & Deterministic UNIX static archives из MELF ET\_REL с System-V/GNU-style symbol index.\\ +\code{mcpu-nm} & Symbol-table inspection для объектов, executables, shared objects и archives.\\ +\code{mcpu-objdump} & Section/relocation inspection и дизассемблирование через тот же decoder, который используется CPU runtime.\\ +\code{mcpu-strip} & Консервативное удаление только non-runtime symbol metadata; link/runtime-critical sections сохраняются.\\ +\code{mcpu-objcopy} & MELF-to-MELF structural copy и safe editing non-ALLOC metadata; runtime-visible image защищён.\\ +\bottomrule +\end{tabularx} + +\begin{principle} +MELF уже является native object/executable container виртуальной MCPU. Поэтому \code{mcpu-objcopy} не имеет режимов flat binary, Intel HEX, S-record, VMA/LMA или подготовки firmware image для SPI NOR. Metadata-only edit обязан сохранять guest-visible allocated image и frozen ABI v3 execution semantics. +\end{principle} + +Английские и русские manual pages устанавливаются как section 1 для каждой команды; обзор архитектуры и toolchain доступен как \code{libmcpu(7)}. + +\section{Инварианты реализации} +\begin{enumerate} +\item PROGRAM machine fields всегда MSB-first; data representation не смешивается с code representation. +\item Register address и PTR64 offset не являются host pointers. +\item Bounds/permission validation precedes guest-visible writes, где instruction contract требует atomicity. +\item DATA имеет один process-owned 16-byte NULL guard; ET_DYN не получает собственного guard. +\item HEAP имеет immutable configured limit и minimum break 16; released bytes zeroed on shrink. +\item ABI-v1 static executable полностью linked; ordinary loader его не relink-ит. +\item ABI-v2/v3 startup linking завершается до \code{_start}; после старта symbol lookup отсутствует. +\item LOCAL ET_DYN references runtime-rebased, но никогда не interpose. +\item Lifecycle boundaries определяются guest \code{END/EXIT}; stage actions лишь наблюдают/модифицируют state на host side. +\end{enumerate} + + +\appendix +\section{Полный справочник директив mcpu-as} +Этот appendix перечисляет assembler directives и специальные формы, которые являются частью текущего source contract. Детальная grammar выражений и ограничения sections приведены в основной части документа. +\input{libmcpu-assembler-directives-table.tex} + +\section{Полная таблица OPCODE16} +Таблица перечисляет все значения public \code{cpu_opcode}. Каждый opcode однозначно задаёт число и тип последующих operands; PROGRAM machine fields сериализуются MSB-first. Operand spellings показаны в терминах assembler ABI. +\input{libmcpu-opcode-table.tex} + +\section{PSW selectors и RCONST} +\subsection{PSW bit selectors} +\input{libmcpu-psw-table.tex} +\subsection{RCONST selectors} +\input{libmcpu-rconst-table.tex} + +\section{Краткая памятка ABI} +\par\medskip +\noindent\begin{tabularx}{\textwidth}{@{}L{42mm}Y@{}} +\toprule +Register file & 512 KiB, overlapping REG24 views\\ +Return Value Area & top 8192 bytes, scalar result at \code{0x80000-size}\\ +PTR64 & SEG4:OFF60; DATA=0, STACK=1, PROGRAM=2, CONTROL=3, INPUT=4, OUTPUT=5, TRACE=6, HEAP=7\\ +CALL alignment & 16 bytes; CALL pushes 8-byte return PC; caller owns argument block\\ +PSW & bits 0..15 volatile across call; TF/IF/MODE preserved if changed\\ +DATA guard & process offsets 0..15 zero\\ +HEAP & begin/break minimum 16; \code{_end=HEAP:16}\\ +Startup & 32-byte McpuStartup + argv PTR64 array + strings; persistent\\ +Low-level lifecycle & mandatory \code{_start}, \code{_life}; optional \code{_fini}; \code{e_entry=_start}\\ +HLL PROGRAM & defining ET_REL: \code{main+fini}; automatic \code{crt0.o+crti.o+crtn.o}\\ +HLL MODEL & defining ET_REL: \code{init+life+fini}; automatic \code{crt1.o+crti.o+crtn.o}\\ +Dynamic & frozen normal ABI v3 with SONAME/NEEDED; ABI v2 remains historical explicit-preload input format\\ +\bottomrule +\end{tabularx} + +\end{document} diff --git a/doc/src/libmcpu-doc-common.tex b/doc/src/libmcpu-doc-common.tex new file mode 100644 index 0000000..c588991 --- /dev/null +++ b/doc/src/libmcpu-doc-common.tex @@ -0,0 +1,169 @@ +% Common memoir-based style for LibMCPU reference manuals. +% Shared XeLaTeX style for LibMCPU reference manuals. + + +\usepackage{fontspec} +\defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase} +\setmainfont{Roboto} +\setsansfont{Roboto} +\setmonofont{DejaVu Sans Mono}[Scale=0.86] + +\usepackage{polyglossia} +\setdefaultlanguage{russian} +\setotherlanguage{english} +\usepackage{microtype} +\usepackage[strings]{underscore} +\usepackage{xcolor} + +\definecolor{McpuBlue}{HTML}{1F3556} +\definecolor{McpuBlue2}{HTML}{526A89} +\definecolor{McpuGreen}{HTML}{2F8A57} +\definecolor{McpuGreenBg}{HTML}{EDF6F0} +\definecolor{McpuGrayBg}{HTML}{F4F6F8} +\definecolor{McpuBlueBg}{HTML}{EEF3F8} +\definecolor{McpuGray}{HTML}{5C6673} +\definecolor{McpuRule}{HTML}{B9C6D8} +\definecolor{McpuCode}{HTML}{F6F7F8} +\definecolor{McpuUrl}{HTML}{B40B22} + +% A4 layout shared with the restyled LibMCPU book: generous text block, +% but enough outer whitespace for technical figures and annotations. +\setstocksize{297mm}{210mm} +\settrimmedsize{297mm}{210mm}{*} +\setlrmarginsandblock{18mm}{15mm}{*} +\setulmarginsandblock{17mm}{19mm}{*} +\setheadfoot{9mm}{8mm} +\setheaderspaces{4mm}{*}{*} +\checkandfixthelayout + +\usepackage{graphicx} +\usepackage{tikz} +\usetikzlibrary{arrows.meta,positioning,fit,calc,shapes.geometric,decorations.pathreplacing} +\usepackage{array} +\usepackage{booktabs} +\usepackage{tabularx} +\usepackage{longtable} +\usepackage{multirow} +\usepackage{enumitem} +\setlist{nosep,leftmargin=2em} +\setlist[itemize]{label=\textcolor{McpuBlue}{\small\textbullet}} +\usepackage[most]{tcolorbox} +\usepackage{listings} +\usepackage{fvextra} +\usepackage{hyperref} +\usepackage{bookmark} +\usepackage{needspace} +\usepackage{ragged2e} + +\hypersetup{ + unicode=true, + colorlinks=true, + linkcolor=McpuBlue, + urlcolor=McpuUrl, + citecolor=McpuBlue, + pdfauthor={Andrew V. Kosteltsev}, + pdflang={ru-RU} +} +\bookmarksetup{open,numbered} + +% memoir section and page styles. +\setsecheadstyle{\Large\sffamily\bfseries\color{McpuBlue}} +\setsubsecheadstyle{\large\sffamily\bfseries\color{McpuBlue}} +\setsubsubsecheadstyle{\normalsize\sffamily\bfseries\color{McpuBlue2}} +\setbeforesecskip{1.45\onelineskip} +\setaftersecskip{0.55\onelineskip} +\setbeforesubsecskip{1.05\onelineskip} +\setaftersubsecskip{0.35\onelineskip} +\setbeforesubsubsecskip{0.8\onelineskip} +\setaftersubsubsecskip{0.25\onelineskip} + +\makepagestyle{libmcpu} +\makeoddhead{libmcpu}{\small\sffamily\color{McpuGray}\DocShortTitle}{}{\small\sffamily\color{McpuGray}LibMCPU} +\makeoddfoot{libmcpu}{}{\small\sffamily\thepage}{} +\makeheadrule{libmcpu}{\textwidth}{0.3pt} +\pagestyle{libmcpu} + +\setlength{\parindent}{1.25em} +\setlength{\parskip}{0.20em} +\setlength{\cftsectionnumwidth}{3.2em} +\setlength{\cftsubsectionnumwidth}{4.2em} +\setlength{\cftsubsubsectionnumwidth}{5.0em} + +\newtcolorbox{principle}{enhanced,breakable,colback=McpuGreenBg,colframe=McpuGreen,boxrule=0pt,leftrule=3pt,arc=0pt,left=10pt,right=10pt,top=8pt,bottom=8pt,before skip=8pt,after skip=8pt} +\newtcolorbox{normative}{enhanced,breakable,colback=McpuBlueBg,colframe=McpuBlue,boxrule=0pt,leftrule=3pt,arc=0pt,left=10pt,right=10pt,top=8pt,bottom=8pt,before skip=8pt,after skip=8pt} +\newtcolorbox{warningbox}{enhanced,breakable,colback=yellow!8,colframe=orange!75!black,boxrule=0pt,leftrule=3pt,arc=0pt,left=10pt,right=10pt,top=8pt,bottom=8pt,before skip=8pt,after skip=8pt} +\newtcolorbox{codebox}{enhanced,breakable,colback=McpuCode,colframe=McpuRule,boxrule=0.4pt,arc=1pt,left=7pt,right=7pt,top=5pt,bottom=5pt,before skip=7pt,after skip=7pt} + +\lstdefinelanguage{MCPUAsm}{ + morekeywords={LDI,LDR,LDC,MOV,IADD,ISUB,IAND,IOR,IXOR,INOT,ICMP,ITEST,ISHL,ISHR,ISAL,ISAR,IROL,IROR,IRCL,IRCR,ISHLN,ISHRN,ISALN,ISARN,IROLN,IRORN,IRCLN,IRCRN,IINC,IDEC,IXCHG,ICPY,ICVT,IMUL,ISMUL,IDIV,ISDIV,IBITSLEN,RADD,RSUB,RMUL,RDIV,RSQRT,RSIN,RCOS,RTAN,RASIN,RACOS,RATAN,RLOG,RLOG10,RLOG2,REXP,RSINH,RCOSH,RTANH,RPOW,RHYPOT,RATAN2,RNEG,RABS,RCMP,RCPY,RCVT,RFLOOR,RCEIL,RROUND,RFREXP,RLDEXP,RLOGB,RMODF,RREMAIN,RLOG1P,REXPM1,RASINH,RACOSH,RATANH,ITOR,UITOR,RTOI,RTOUI,RISNEG,RSIGN,RISIND,RISNAN,RISNANMAX,RISNANMIN,RISINF,RISSIGNULL,RCONST,CADD,CSUB,CMUL,CDIV,CEXP,CSIN,CCOS,CSINH,CCOSH,CLOG,CSQRT,CPOW,CREAL,CIMAG,CRTOC,CGEN,CPOLAR,CCONJ,CABS,CNORM,CARG,CISEQ,CISNE,CCPY,CCVT,JMP,JSET,JCLR,JMPP,CALL,CALLP,RET,PUSH,POP,LDSP,STSP,ADJSP,PUSHFP,SETFP,RESTSP,POPFP,LDFP,STFP,LEAFP,SETSP,LOAD,STORE,LEA,LEASP,LOADP,STOREP,PTRADD,PTRSUB,PTRDIFF,BMOV,BMOVI,BSWAP,END,EXIT,HALT,SYSCALL}, + sensitive=false, + morecomment=[l]{;}, + morecomment=[l]{\#}, + morestring=[b]" +} +\lstset{ + language=MCPUAsm, + basicstyle=\ttfamily\small, + columns=fullflexible, + keepspaces=true, + showstringspaces=false, + breaklines=true, + breakatwhitespace=false, + postbreak=\mbox{\textcolor{McpuGray}{$\hookrightarrow$}\space}, + frame=single, + rulecolor=\color{McpuRule}, + backgroundcolor=\color{McpuCode}, + aboveskip=7pt, + belowskip=7pt, + framerule=0.4pt +} + +% Long shell commands and verbatim data must never run outside the page. +\DefineVerbatimEnvironment{McpuCommand}{Verbatim}{fontsize=\small,frame=single,framesep=3mm,rulecolor=\color{McpuBlue!35},breaklines=true,breakanywhere=true,breaksymbolleft={},breaksymbolright={},breakindent=1.5em} +\DefineVerbatimEnvironment{McpuCode}{Verbatim}{fontsize=\small,frame=single,framesep=3mm,rulecolor=\color{McpuGray!35},breaklines=true,breakanywhere=true,breaksymbolleft={},breaksymbolright={}} + +\newcolumntype{Y}{>{\raggedright\arraybackslash}X} +\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}} +\urlstyle{tt}\newcommand{\code}[1]{\nolinkurl{#1}} +\newcommand{\field}[1]{\texttt{#1}} +\newcommand{\MCPU}{LibMCPU} +\newcommand{\MELF}{MELF} +\newcommand{\PTR}{PTR64} +\newcommand{\REG}{REG24} +\newcommand{\term}[2]{\textbf{#1} (\textit{#2})} +\newcommand{\figlabel}[1]{\sffamily\small\color{McpuGray}#1} +\newcommand{\tabhead}[1]{\sffamily\bfseries #1} + +\newcommand{\DocTitlePage}[3]{% + \begin{titlingpage} + \thispagestyle{empty} + \vspace*{30mm} + {\sffamily\fontsize{29}{34}\selectfont\bfseries\color{McpuBlue} #1\par} + \vspace{8mm} + {\sffamily\fontsize{16}{21}\selectfont\color{McpuBlue2} #2\par} + \vspace{4mm} + {\sffamily\large\color{McpuGray} #3\par} + \vfill + \begin{tcolorbox}[width=0.86\textwidth,colback=McpuGreenBg,colframe=McpuGreen,boxrule=0pt,leftrule=4pt,arc=0pt] + \textbf{Нормативное техническое руководство LibMCPU.} Документ описывает текущий архитектурный контракт и намеренно не привязан к номеру выпуска пакета. + \end{tcolorbox} + \vfill + {\small\sffamily\color{McpuGray}Техническая редакция, октябрь 2026\par} + \end{titlingpage} +} +\setcounter{tocdepth}{2} +\setcounter{secnumdepth}{3} +% Reference manuals have no chapter level; memoir otherwise prefixes sections +% with the implicit chapter number 0. +\renewcommand{\thesection}{\arabic{section}} +\renewcommand{\thesubsection}{\thesection.\arabic{subsection}} +\renewcommand{\thesubsubsection}{\thesubsection.\arabic{subsubsection}} +\renewcommand{\thefigure}{\arabic{figure}} +\renewcommand{\thetable}{\arabic{table}} +\renewcommand{\contentsname}{Содержание} +\newcommand{\ManualIntro}[1]{\begin{principle}\textbf{Статус документа.} #1\end{principle}} +\newcommand{\smallcapslabel}[1]{\textcolor{McpuBlue}{\textbf{#1}}} +\newenvironment{compactdesc}{\begin{description}[style=multiline,leftmargin=48mm,labelwidth=44mm,labelsep=4mm,labelindent=0pt,font=\normalfont\bfseries\color{McpuBlue}]}{\end{description}} + +\emergencystretch=4em\tolerance=1800 +\raggedbottom diff --git a/doc/src/libmcpu-hll-crt-contract-ru.tex b/doc/src/libmcpu-hll-crt-contract-ru.tex new file mode 100644 index 0000000..c2a3c3d --- /dev/null +++ b/doc/src/libmcpu-hll-crt-contract-ru.tex @@ -0,0 +1,181 @@ +\section{HLL Runtime ABI: PROGRAM, MODEL, CRT и MCPU Libc} +\label{sec:hll-crt-contract} + +Этот раздел фиксирует подписанный HLL Runtime ABI. Он описывает границу между объектами, которые в будущем генерирует \code{mcpu-cc}, существующим \code{mcpu-ld}, CRT, MCPU Libc и уже действующим MELF lifecycle. Контракт не зависит от номера выпуска LibMCPU. + +\begin{principle} +\textbf{Разделение уровней.} HLL-функции \code{main()}, \code{init()}, \code{life()} и \code{fini()} являются обычными функциями CALL ABI и завершаются \code{RET}. Машинные точки \code{_start}, \code{_life}, \code{_fini}, а также lifecycle-инструкции \code{END}/\code{EXIT} принадлежат CRT и MELF runtime. Пользовательская HLL-функция не должна исполнять raw \code{END} или \code{EXIT} из открытого CALL frame. +\end{principle} + +\subsection{Целевая integer-модель HLL ABI} + +HLL ABI использует фиксированные целевые типы \code{int8/int16/int32/int64}, соответствующие unsigned-типы и \code{PTR64}. Исторические C-имена \code{char/short/int/long} не определяют бинарный ABI. \code{PTR64} --- обозначение бинарного pointer object, а не синоним \code{int64*}. + +Канонические compiler-visible пределы публикуются препроцессором целевой среды. Для жизненного цикла важны определения: +\begin{lstlisting}[language={}] +#define LIFE_END 0 +#define LIFE_CONTINUE (-__INT32_MAX__ - 1) +\end{lstlisting} +Таким образом минимальное значение signed int32 выражается непосредственно через target predefined macro \code{__INT32_MAX__}. + +\subsection{Две формы законченной HLL-программы} + +Исходный compilation unit определяет ровно одну из двух форм: +\begin{center} +\begin{tabularx}{0.94\textwidth}{@{}L{27mm}L{66mm}Y@{}} +\toprule +\textbf{Форма} & \textbf{Функции HLL ABI} & \textbf{Назначение}\\ +\midrule +PROGRAM & \code{int32 main(int64 argc, PTR64 argv)}; optional source-level \code{void fini(void)} & однократная обычная программа\\ +MODEL & \code{int32 init(int64 argc, PTR64 argv)}; \code{int32 life(void)}; optional source-level \code{void fini(void)} & модель с INIT и повторяющимися LIFE-фазами\\ +\bottomrule +\end{tabularx} +\end{center} + +Frontend нормализует optional source-level \code{fini}: если программист её не определил, compiler создаёт пустую strong GLOBAL/FUNC \code{fini}. Поэтому defining ET_REL всегда содержит: +\begin{itemize} +\item PROGRAM: strong GLOBAL/FUNC \code{main} и \code{fini}; +\item MODEL: strong GLOBAL/FUNC \code{init}, \code{life} и \code{fini}. +\end{itemize} +Все complete-program definitions одной формы принадлежат \textbf{одному} defining ET_REL. Другие translation units могут их объявлять, но не определять. \code{main} несовместим с парой \code{init/life}; неполная или разделённая между несколькими ET_REL форма является ошибкой final link. + +\subsection{Автоматическая классификация в mcpu-ld} + +Для final ET_EXEC \code{mcpu-ld} классифицирует обычные входные ET_REL до разрешения библиотек. Никаких ключей \code{--program} или \code{--model} нет: +\begin{center} +\begin{tabularx}{0.88\textwidth}{@{}L{54mm}Y@{}} +\toprule +\textbf{Определения defining ET_REL} & \textbf{Режим}\\ +\midrule +\code{main + fini} & HLL PROGRAM\\ +\code{init + life + fini} & HLL MODEL\\ +\code{_start/_life[/_fini]} без HLL roots & low-level MELF executable; automatic CRT не добавляется\\ +\bottomrule +\end{tabularx} +\end{center} + +Смешение raw lifecycle definitions с HLL complete-program roots запрещено. Символы из \code{-l...} не используются для классификации и потому библиотека не может случайно превратить link в PROGRAM или MODEL. + +После классификации linker автоматически вставляет CRT из активного MCPU system root: +\begin{McpuCode} +PROGRAM: <root>/lib/mcpu/crt0.o crti.o <user objects/libraries> crtn.o +MODEL: <root>/lib/mcpu/crt1.o crti.o <user objects/libraries> crtn.o +\end{McpuCode} +Отсутствие любого обязательного CRT object является fatal link error. Переход к другому root или деградация в low-level link не выполняются. + +\subsection{Распределение ответственности CRT} + +Канонические исходники принадлежат LibMCPU и устанавливаются как target runtime objects: +\begin{center} +\begin{tabularx}{0.92\textwidth}{@{}L{26mm}Y@{}} +\toprule +\code{crt0.o} & PROGRAM adapter; формирует \code{_start == _life} в \code{.text} и вызывает \code{main}.\\ +\code{crt1.o} & MODEL adapter; формирует \code{_start} в \code{.init} и \code{_life} в \code{.text}, вызывает \code{init}/\code{life}.\\ +\code{crti.o} & общая startup state/validation и вызовы начала MCPU Libc.\\ +\code{crtn.o} & обязательная HLL \code{_fini}, PRE_RUNTIME/RUNNING branch и graceful finalization.\\ +\bottomrule +\end{tabularx} +\end{center} + +HLL ET_EXEC всегда получает \code{_fini}, даже если source-level \code{fini()} была опущена и нормализована frontend-ом в пустую функцию. + +\subsection{McpuStartup, argc и argv} + +Loader строит \code{McpuStartup} один раз до первой guest instruction. Заголовок, массив \code{argv[argc+1]} и строки аргументов остаются действительными до конца процесса. CRT проверяет version/size/representation, затем проверяет, что \code{argc} не превышает \code{__INT64_MAX__}, и преобразует unsigned startup field в target signed \code{int64}. Существующий \code{PTR64 argv} переиспользуется; MCPU Libc не копирует и не владеет \code{argc/argv}. + +Для \code{main()} и \code{init()} CRT создаёт обычный 16-байтовый CALL argument block: +\begin{center} +\begin{tabularx}{0.72\textwidth}{@{}L{24mm}L{30mm}Y@{}} +\toprule +\textbf{До CALL} & \textbf{Объект} & \textbf{После push return PC callee видит}\\ +\midrule +SP+0 & int64 \code{argc} & SP+8\\ +SP+8 & PTR64 \code{argv} & SP+16\\ +\bottomrule +\end{tabularx} +\end{center} +Возврат \code{int32} из \code{main/init/life} находится в канонической Return Value Area \code{r32@0x7fffc}. + +\subsection{Внутренние hooks MCPU Libc} + +CRT использует ровно четыре reserved internal hooks: +\begin{lstlisting}[language={}] +void __mcpu_libc_init(void); +void __mcpu_init_array(void); +void __mcpu_fini_array(void); +void __mcpu_libc_fini(void); +\end{lstlisting} +Это обычные no-argument CALL ABI functions с \code{RET}. Startup order после успешной validation: +\begin{McpuCode} +__mcpu_libc_init() +__mcpu_init_array() +main(...) или init(...) +\end{McpuCode} +Graceful HLL finalization в состоянии RUNNING: +\begin{McpuCode} +fini() +__mcpu_fini_array() +__mcpu_libc_fini() +END +\end{McpuCode} +Physical ABI init/fini arrays и constructor priorities могут развиваться отдельно; наличие hooks уже фиксирует границу CRT и Libc. + +Обычная ABI-v3 DT_NEEDED библиотека MCPU Libc имеет право предоставить эти четыре symbols. Explicit preload не имеет права их подменять. Complete-program roots \code{main/init/life/fini}, lifecycle symbols и CRT-private names также не являются пользовательским interposition API. + +\subsection{PRE_RUNTIME и RUNNING} + +CRT хранит private logical state \code{PRE_RUNTIME} / \code{RUNNING}; его физическое представление не является public ABI. До успешного завершения \code{__mcpu_libc_init()} и \code{__mcpu_init_array()} состояние остаётся PRE_RUNTIME. + +Если validation startup area или диапазона \code{argc} завершается ошибкой, CRT выполняет graceful \code{EXIT(startup_failure_status)}. Supervisor входит в \code{_fini}, но PRE_RUNTIME branch выполняет только \code{END}: пользовательская \code{fini}, fini-array и libc-fini не вызываются. Namespace startup-failure status не выводится из host errno. + +После startup hooks CRT устанавливает RUNNING. С этого момента normal graceful EXIT всегда приводит к полному HLL finalization. Поэтому ненулевой возврат \code{init()} уже находится в RUNNING и вызывает пользовательскую \code{fini()}; MODEL fini должна допускать частично инициализированную модель. + +\subsection{PROGRAM lifecycle} + +PROGRAM не имеет отдельной \code{.init}. \code{_start == _life} находится в \code{.text}. После validation и startup hooks CRT вызывает +\begin{lstlisting}[language={}] +int32 main(int64 argc, PTR64 argv); +\end{lstlisting} +и после \code{RET} выполняет \code{EXIT(main_status)}. Дополнительного LIFE \code{END} после \code{main()} нет. + +\subsection{MODEL lifecycle} + +MODEL использует \code{_start} в \code{.init} и \code{_life} в \code{.text}. В INIT CRT вызывает +\begin{lstlisting}[language={}] +int32 init(int64 argc, PTR64 argv); +\end{lstlisting} +Возврат 0 создаёт ровно одну INIT boundary через \code{END}. Ненулевой status выполняет \code{EXIT(status)} и переходит к полной HLL finalization, поскольку runtime уже RUNNING. + +Каждая LIFE-фаза является отдельным обычным вызовом: +\begin{lstlisting}[language={}] +int32 life(void); +\end{lstlisting} +Return namespace фиксирован: +\begin{center} +\begin{tabularx}{0.94\textwidth}{@{}L{44mm}L{34mm}Y@{}} +\toprule +\textbf{int32 return} & \textbf{Имя} & \textbf{Действие CRT}\\ +\midrule +\code{-__INT32_MAX__ - 1} & \code{LIFE_CONTINUE} & выполнить ровно один LIFE \code{END}; после resume снова вызвать \code{life()}\\ +0 & \code{LIFE_END} & \code{EXIT(0)} без дополнительного END\\ +1..\code{__INT32_MAX__} & error status & \code{EXIT(status)} без дополнительного END\\ +\code{-__INT32_MAX__}..-1 & reserved & не использовать без будущего расширения ABI\\ +\bottomrule +\end{tabularx} +\end{center} +Это гарантирует, что естественное завершение модели не создаёт фиктивной последней LIFE boundary и, например, лишней строки TRACE. + +\subsection{HLL finalization и сохранение status} + +В RUNNING каноническая \code{_fini} концептуально выполняет: +\begin{McpuCode} +CALL fini +CALL __mcpu_fini_array +CALL __mcpu_libc_fini +END +\end{McpuCode} +Original EXIT status сохраняется runtime через всю finalization sequence. Второй \code{EXIT} из \code{_fini} не выполняется. Диагностическая остановка \code{mcpu-run --steps=...} не является graceful exit и не синтезирует HLL finalization. + +\begin{normative} +\textbf{Главный итог.} HLL-программа определяет только обычные functions. \code{mcpu-ld} распознаёт нормализованную форму ET_REL и выбирает CRT автоматически. CRT владеет MELF lifecycle, MCPU Libc владеет библиотечным runtime, а loader владеет persistent startup area. Ни один слой не дублирует ответственность соседнего. +\end{normative} diff --git a/doc/src/libmcpu-isa-coverage-ru.tex b/doc/src/libmcpu-isa-coverage-ru.tex new file mode 100644 index 0000000..0851aea --- /dev/null +++ b/doc/src/libmcpu-isa-coverage-ru.tex @@ -0,0 +1,66 @@ +\section{Актуальное покрытие LibMPU и семейства ISA} +Вычислительное ядро ISA не является минимальным proof-of-concept. Оно содержит полноценные integer, Real и Complex families, а также отдельные группы управления потоком, стеком, указателями и runtime boundary. Этот раздел фиксирует именно архитектурную группировку; точные значения всех OPCODE16 приведены в приложении B и генерируются из public \code{cpu_opcode} в \code{include/libmcpu.h}. + +\subsection{System и data-movement family} +Системная группа содержит \code{NOP}, \code{HALT}, lifecycle instruction \code{END}, graceful \code{EXIT}, zero-operand \code{SYSCALL} и отдельную PSW/status subgroup: \code{SETF}, \code{CLRF}, \code{CLRCPUF}, \code{CLRMPUF}. Data-movement/address family содержит \code{MOV}, \code{CLR}, \code{SWAP}, raw-byte \code{BSWAP}, immediate forms \code{LDI/LDR/LDC}, direct DATA access \code{LOAD/STORE}, pointer construction/dereference \code{LEA/LEASP/LOADP/STOREP}, pointer arithmetic \code{PADD/PADDI/PSUB/PSUBI/PDIFF} и block move \code{BMOV/BMOVI}. + +\begin{normative} +\textbf{Тип operands задаёт opcode.} REG24 не содержит integer/Real/Complex tag. Поэтому один и тот же physical register может последовательно участвовать в разных domains, но assembler проверяет semantic view там, где signature требует \code{c<bits>} либо конкретный \code{r64} carrier. Executor независимо повторяет width/signature validation и не доверяет исходному тексту assembler. +\end{normative} + +\subsection{Integer family} +Integer operations работают с physical widths от 8 до 65536 bits. Базовые arithmetic/logical operations включают \code{IADD/IADC/ISUB/ISBB}, \code{IAND/IOR/IXOR/INOT}, \code{INEG}, compare/test, increment/decrement и exchange. Shift/rotate представлены как single-bit forms и variants с count в \code{r64}: logical/arithmetic shifts, ordinary rotates и rotates through carry. + +Width-changing operations намеренно разделены на unsigned copy \code{ICPY} и signed conversion \code{ICVT}. Full multiply возвращает product расширенной ширины через \code{IMUL/ISMUL}; division имеет отдельные unsigned/signed четырехоперандные формы \code{IDIV/ISDIV} с quotient и remainder. \code{IBITSLEN} выдаёт длину integer в bits в \code{r64}. + +Модульная арифметика LibMPU (например modular multiply/power и GCD-related higher-level routines) намеренно не превращена в CPU opcodes. Она остаётся library-level functionality: ISA предоставляет primitives, а не дублирует всю API-поверхность LibMPU. + +\subsection{Real family} +Real arithmetic работает непосредственно с LibMPU objects требуемой physical precision. Простые operations (copy, conversion, add/sub/mul/div, sign/abs, decomposition, predicates) поддерживают архитектурно допустимые Real widths. Transcendental family ограничена тем же верхним пределом, что и настроенная LibMPU math backend; в проверенной конфигурации проекта это real16384. + +В ISA присутствуют: +\begin{itemize} +\item arithmetic и square root; +\item trigonometric/inverse trigonometric functions; +\item logarithms, exponentials, hyperbolic и inverse hyperbolic functions; +\item \code{pow}, \code{hypot}, \code{atan2}; +\item \code{floor/ceil/round}, \code{frexp/ldexp/logb/modf/remainder}; +\item signed/unsigned integer-to-Real и Real-to-integer conversions; +\item classification/predicate family для sign, IND, NaN classes, infinity и signed zero; +\item \code{RCONST}, создающий predefined values непосредственно в precision destination. +\end{itemize} + +\begin{principle} +Широкий Real register не проходит через host \code{double}. Там, где instruction содержит immediate, exactness проверяется assembler-ом до emission; для произвольной высокой точности значение хранится как target Real object в data-like section и загружается byte-preserving instruction \code{LOAD/LOADP}. +\end{principle} + +\subsection{Complex family} +Complex register view имеет полный physical width от c64 до c65536, а каждая компонента занимает половину. Arithmetic \code{CADD/CSUB/CMUL/CDIV} использует LibMPU complex primitives. Math family содержит \code{CEXP/CSIN/CCOS/CSINH/CCOSH/CLOG/CSQRT/CPOW}; для функций, требующих math backend, предел component precision соответствует Real math limit (то есть c32768 при real16384 components). + +Отдельно представлены extraction/construction/conversion operations: \code{CREAL/CIMAG}, \code{CRTOC}, \code{CGEN}, \code{CPOLAR}, conjugation, absolute value/norm/argument, equality predicates и copy/precision conversion. Для mixed-domain signatures Real component register обязан иметь ровно половинную physical width соответствующего Complex object. + +\subsection{Control-flow и stack family} +Прямые targets представлены CODE64; indirect targets - PROGRAM PTR64 в \code{r64}. Branch family состоит из \code{JMP}, \code{JSET/JCLR}, \code{JMPP}; call family - \code{CALL}, \code{CALLP}, \code{RET}. Условия \code{JZ/JNZ/JC/JNC/...} являются assembler aliases над PSW bits, а не отдельными opcodes. + +Stack family содержит object push/pop, SP-relative и FP-relative load/store, signed \code{ADJSP}, canonical frame operations \code{PUSHFP/SETFP/RESTSP/POPFP}, \code{LEAFP} и validated \code{SETSP}. Эти instructions работают с raw STACK offsets/PTR64 и не вводят скрытого frame metadata в CPU. + + +\subsection{Явное управление arithmetic/status flags} +PSW разделён на два writable byte domains: CPU flags \code{PSW[7:0]} и MPU flags \code{PSW[15:8]}. Инструкции \code{SETF flag} и \code{CLRF flag} изменяют ровно один именованный bit из диапазона AF..INXF и сохраняют все остальные bits PSW. \code{CLRCPUF} очищает только \code{PSW[7:0]}, а \code{CLRMPUF} --- только \code{PSW[15:8]}. + +\begin{normative} +HF, TF, IF и многобитовое поле MODE не принадлежат writable arithmetic/status domain. \code{SETF/CLRF} обязаны отвергать selectors HF/TF/IF; bulk-clear instructions также не изменяют PSW bits 16 и выше. \code{JSET/JCLR} при этом сохраняют более широкий read-only selector contract и могут проверять HF/TF/IF. +\end{normative} + +Явная установка CF предназначена, в частности, для подготовки входного carry/borrow state перед \code{IADC/ISBB} и rotate-through-carry family. Сами integer instructions сохраняют установленную LibMPU semantics; новые flag instructions только дают assembler-программе прямой способ подготовить их входное состояние. + +\subsection{PSW-neutral conversion class} +Conversion-class instructions сохраняют incoming \code{PSW[15:0]}, даже если внутренний LibMPU helper меняет library flags. К этой категории относятся непосредственные numeric loads и width/domain conversions, в частности \code{LDI}, \code{LDR}, \code{LDC}, \code{ICVT}, \code{RCVT}, \code{ITOR}, \code{UITOR}, \code{RTOI}, \code{RTOUI}, \code{CCVT}, \code{CRTOC}. Arithmetic instructions, напротив, публикуют обычные LibMPU status effects. + +Это различие важно для compiler backend: преобразование representation не должно разрушать condition state предыдущего сравнения, тогда как arithmetic operation по определению может менять flags. + +\subsection{Неинструкционные функции} +Text parsing/formatting, format introspection, diagnostics и library context management не являются CPU operations. Они принадлежат assembler, LibMPUIO, будущей guest library либо host runtime. Такая граница сохраняет ISA вычислительной и не привязывает bytecode к host textual API. + +\subsection{Regression contract} +Нормативное поведение instruction family проверяется не только unit tests decoder-а. Regression suite сравнивает CPU execution с соответствующими LibMPU primitives, проверяет malformed widths/operands, exact-immediate diagnostics, PSW preservation для conversions, pointer/stack faults и no-partial-write invariants. Отдельный пакет model examples дополнительно проверяет композицию ISA + CALL ABI + MELF + loader на длинном execution path. diff --git a/doc/src/libmcpu-melf-abi-ru.tex b/doc/src/libmcpu-melf-abi-ru.tex new file mode 100644 index 0000000..e7a73b3 --- /dev/null +++ b/doc/src/libmcpu-melf-abi-ru.tex @@ -0,0 +1,661 @@ +\documentclass[10pt,a4paper,oneside,openany]{memoir} +\newcommand{\DocShortTitle}{LibMCPU MELF ABI} +\input{libmcpu-doc-common.tex} +\hypersetup{ + pdftitle={LibMCPU MELF ABI}, + pdfsubject={Формат объектов и исполняемых файлов MCPU, контракт компоновщика и загрузчика} +} +\begin{document} + +\DocTitlePage + {LibMCPU MELF ABI} + {Формат объектов и исполняемых файлов MCPU, контракт компоновщика (linker) и загрузчика (loader)} + {Самостоятельное справочное руководство; нумерация разделов начинается с 1} + +\ManualIntro{Документ является самостоятельным описанием текущего MELF-контракта LibMCPU и намеренно не связан с номером выпуска пакета. Он фиксирует статический ABI v1, исторический explicit-preload ABI v2, frozen normal dynamic ABI v3 с SONAME/NEEDED, lifecycle, STACK/HEAP, MCPU static archives и partial ET_REL linking. HLL PROGRAM/MODEL classification и automatic CRT insertion описаны как linker layer над MELF container ABI.} + +\begin{principle} +\textbf{MELF --- не системный ELF.} Формат намеренно использует сигнатуру \code{_ELF}, частное значение \code{e_machine=MCPU} и специфические для MCPU секции и заголовков сегментов (Program Headers). Ядро host-системы и системный \code{ld.so} не участвуют в запуске. \code{mcpu-ld} и \code{mcpu-run} являются частями одного целевого ABI. +\end{principle} + +\tableofcontents +\clearpage + +\section{Назначение и границы формата} + +MELF хранит три вида файлов MCPU: +\begin{itemize} +\item перемещаемый объект (\term{relocatable object}{перемещаемый объект}) \code{ET_REL}; +\item исполняемый образ (\term{executable}{исполняемый файл}) \code{ET_EXEC}; +\item разделяемый объект (\term{shared object}{разделяемый объект}) \code{ET_DYN}, явно загружаемый при запуске программы. +\end{itemize} + +От ELF заимствованы общие идеи заголовков Ehdr, Shdr и Phdr, таблиц символов и строк, а также relocation-записей RELA. Архитектурные пространства памяти, точки входа lifecycle и метаданные профиля целевой архитектуры определяются самим MCPU ABI. + +Текущая реализация использует только MELF64. Имена и структуры MELF32 зарезервированы, но ABI v1, ABI v2 и ABI v3 их не принимают. C-структуры из \code{mcpu-elf.h} описывают логические поля, но не являются готовой сериализацией на диске (on-disk serialization): код записи и чтения явно кодирует и декодирует многобайтовые поля с учётом \code{EI_DATA}. + +PROGRAM образует отдельную область с собственным порядком байтов: машинные поля MCPU внутри PROGRAM всегда записываются MSB-first независимо от \code{EI_DATA} контейнера. + +\section{Идентификация, класс и e\_machine} + +\subsection{Поле e\_ident} + +\begin{center} +\begin{tabularx}{0.95\textwidth}{@{}L{28mm}L{28mm}Y@{}} +\toprule +\textbf{Поле} & \textbf{Значение} & \textbf{Смысл}\\ +\midrule +MAG0..3 & \code{'_','E','L','F'} & сигнатура MELF; это не обычный \code{0x7fELF}\\ +EI\_CLASS & 2 & \code{MELF_CLASS_64}\\ +EI\_DATA & 1 или 2 & порядок байтов LSB/MSB для метаданных и объектов данных целевой архитектуры\\ +EI\_VERSION & 1 & текущая версия формата\\ +EI\_OSABI & 64 & \code{MELF_OSABI_MCPU}\\ +EI\_ABIVERSION & 1, 2 или 3 & статический ABI v1 / исторический dynamic ABI v2 / frozen dynamic ABI v3\\ +\bottomrule +\end{tabularx} +\end{center} + +\subsection{e\_machine и e\_flags} + +\code{e_machine = 0x4d43} (ASCII ``MC''). Это частное значение MELF, а не официальное значение системного ELF. В текущем ABI \code{e_flags=0}. Неизвестные биты загрузчик (loader) обязан отвергать, а не молча игнорировать. + +\section{Типы файлов и версии ABI} + +В MELF номер ABI относится не к версии инструмента, а к конкретному контракту файла. Текущий toolchain продолжает читать исторические ABI v1/v2 и выпускает ABI v1 для ET_REL/статического ET_EXEC и frozen ABI v3 для новых dynamic ET_EXEC/ET_DYN. + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{43mm}L{24mm}L{18mm}Y@{}} +\toprule +\textbf{Режим формирования} & \textbf{e\_type} & \textbf{ABI} & \textbf{Назначение}\\ +\midrule +\code{mcpu-as} & ET\_REL & 1 & перемещаемый входной объект\\ +\code{mcpu-ld -r} & ET\_REL & 1 & частично скомпонованный перемещаемый объект\\ +\code{mcpu-ld} & ET\_EXEC & 1 & полностью статический исполняемый файл\\ +исторический dynamic output & ET\_EXEC / ET\_DYN & 2 & explicit-preload startup ABI, сохраняемый загрузчиком для совместимости\\ +\code{mcpu-ld --dynamic} & ET\_EXEC & 3 & главный startup-dynamic исполняемый файл с \code{.dynamic}\\ +\code{mcpu-ld --shared} & ET\_DYN & 3 & shared object с обязательным SONAME и возможными NEEDED\\ +ET\_CORE & зарезервировано & --- & пока не поддерживается\\ +\bottomrule +\end{tabularx} +\end{center} + +ABI v3 является frozen normal dynamic ABI. Его несовместимое изменение требует нового номера ABI. ABI v3 не меняет CPU ISA, CALL ABI, PTR64 или базовые типы RELA; он расширяет startup dynamic-link contract метаданными \code{.dynamic}, SONAME/NEEDED, поиском библиотек и детерминированным runtime symbol scope. + +\section{Заголовок MELF64} + +Сериализованный \code{MElf64_Ehdr} имеет размер 88 байт. Поля точек входа lifecycle содержат полные архитектурные \code{PTR64(PROGRAM,offset)}, а не смещения внутри файла. + +\begin{longtable}{@{}L{32mm}L{20mm}L{102mm}@{}} +\toprule +\textbf{Поле} & \textbf{Размер} & \textbf{Смысл}\\ +\midrule +\endfirsthead +\toprule +\textbf{Поле} & \textbf{Размер} & \textbf{Смысл}\\ +\midrule +\endhead +\code{e_ident} & 16 & сигнатура, класс, порядок данных, версия, OSABI, версия ABI\\ +\code{e_type} & 2 & ET\_REL / ET\_EXEC / ET\_DYN\\ +\code{e_machine} & 2 & MCPU\\ +\code{e_version} & 4 & \code{MELF_EV_CURRENT}\\ +\code{e_entry} & 8 & обязательная точка входа исполняемого файла; всегда \code{_start}\\ +\code{e_init_entry} & 8 & \code{_start}, если существует \code{.init}; иначе 0\\ +\code{e_life_entry} & 8 & обязательный \code{_life}\\ +\code{e_fini_entry} & 8 & необязательный \code{_fini}; иначе 0\\ +\code{e_phoff} & 8 & смещение в файле до таблицы заголовков сегментов (Program Headers)\\ +\code{e_shoff} & 8 & смещение в файле до таблицы заголовков секций (Section Headers)\\ +\code{e_flags} & 4 & в текущем ABI равен 0\\ +\code{e_ehsize} & 2 & 88\\ +\code{e_phentsize} & 2 & 64 для MELF64\\ +\code{e_phnum} & 2 & число записей заголовков сегментов\\ +\code{e_shentsize} & 2 & 64 для MELF64\\ +\code{e_shnum} & 2 & число записей заголовков секций\\ +\code{e_shstrndx} & 2 & индекс секции \code{.shstrtab}\\ +\bottomrule +\end{longtable} + +У \code{ET_REL} окончательных адресов lifecycle ещё нет, поэтому четыре поля точек входа равны нулю. \code{ET_DYN} не является самостоятельной программой и также не имеет исполняемых точек входа lifecycle. + +\section{Архитектурные адреса внутри MELF} + +Для окончательно размещённых загружаемых секций (allocated sections) поле \code{sh_addr} содержит полный \code{PTR64(segment,final_offset)}. Используется следующая таблица селекторов (selectors): + +\begin{center} +\begin{tabularx}{0.84\textwidth}{@{}L{26mm}L{18mm}Y@{}} +\toprule +\textbf{Пространство} & \textbf{SEG} & \textbf{Секции / роль}\\ +\midrule +DATA & 0x0 & \code{.data}, \code{.bss}\\ +STACK & 0x1 & только дескриптор времени выполнения; полезные данные объекта отсутствуют\\ +PROGRAM & 0x2 & \code{.init}, \code{.text}, \code{.fini}\\ +CONTROL & 0x3 & \code{.control}\\ +INPUT & 0x4 & \code{.input}\\ +OUTPUT & 0x5 & \code{.output}\\ +TRACE & 0x6 & \code{.trace}\\ +HEAP & 0x7 & только дескриптор времени выполнения; данных ассемблерного объекта нет\\ +\bottomrule +\end{tabularx} +\end{center} + +В \code{ET_REL} символы и секции используют смещения относительно своей секции и ещё не имеют окончательной базы процесса. В \code{ET_DYN} адреса также остаются относительными к модулю; окончательные базы PROGRAM и DATA назначает компоновщик запуска (startup linker). + +\section{Секции и флаги секций} + +MELF использует типы секций, близкие к ELF: \code{SHT_NULL}, \code{SHT_PROGBITS}, \code{SHT_SYMTAB}, \code{SHT_STRTAB}, \code{SHT_RELA}, \code{SHT_NOBITS}, \code{SHT_DYNSYM} и \code{SHT_DYNAMIC}.\par +Кроме того, определены два специфических для MCPU типа: +\begin{center} +\begin{tabularx}{0.82\textwidth}{@{}L{54mm}Y@{}} +\toprule +\textbf{Тип секции} & \textbf{Числовое значение}\\ +\midrule +\code{SHT_MCPU_TARGET} & 0x70000000\\ +\code{SHT_MCPU_LIFECYCLE} & 0x70000001\\ +\bottomrule +\end{tabularx} +\end{center} + +Загружаемые секции получают обычные признаки R/W/X через \code{SHF_WRITE/ALLOC/EXECINSTR}; тег архитектурного сегмента MCPU кодируется в процессорных битах флагов. + +\begin{center} +\begin{tabularx}{0.94\textwidth}{@{}L{24mm}L{24mm}L{26mm}Y@{}} +\toprule +\textbf{Секция} & \textbf{Тип} & \textbf{Пространство} & \textbf{Атрибуты}\\ +\midrule +.init & PROGBITS & PROGRAM & ALLOC + EXEC\\ +.text & PROGBITS & PROGRAM & ALLOC + EXEC\\ +.fini & PROGBITS & PROGRAM & ALLOC + EXEC\\ +.data & PROGBITS & DATA & ALLOC + WRITE\\ +.bss & NOBITS & DATA & ALLOC + WRITE\\ +.control & PROGBITS & CONTROL & ALLOC, только чтение для guest-программы\\ +.input & PROGBITS & INPUT & ALLOC, только чтение для guest-программы\\ +.output & PROGBITS & OUTPUT & ALLOC + WRITE\\ +.trace & PROGBITS & TRACE & ALLOC + WRITE\\ +\bottomrule +\end{tabularx} +\end{center} + +\code{sh_addralign} задаёт требование выравнивания guest-адреса. Значения 0/1 означают отсутствие специального требования, остальные допустимые значения являются степенями двойки. \code{SHT_NOBITS} занимает архитектурную память, но не содержит данных в файле. + +\section{Объектная модель ET\_REL} + +Ассемблер формирует символы со смещениями относительно секций и явные записи RELA. Типовой путь выглядит так: + +\begin{center} +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,node distance=4mm] +\node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,minimum width=24mm] (src) {исходник .s}; +\node[draw=McpuBlue,rounded corners,fill=McpuGreenBg,right=of src,minimum width=25mm] (as) {mcpu-as}; +\node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,right=of as,minimum width=31mm] (obj) {object.o / ET\_REL}; +\node[draw=McpuBlue,rounded corners,fill=McpuGreenBg,right=of obj,minimum width=24mm] (ld) {mcpu-ld}; +\node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,right=of ld,minimum width=31mm] (exe) {ET\_EXEC}; +\draw[->,thick] (src)--(as); +\draw[->,thick] (as)--(obj); +\draw[->,thick] (obj)--(ld); +\draw[->,thick] (ld)--(exe); +\end{tikzpicture} +\end{center} + +В каждом \code{ET_REL}, включая результат \code{mcpu-ld -r}, имеется ровно одна \code{.mcpu.target}. Окончательная \code{.mcpu.lifecycle} ещё отсутствует: формирование lifecycle принадлежит компоновщику (linker), а в перемещаемом объекте представлено каноническими символами \code{_start}, \code{_life} и необязательным \code{_fini}. + +\section{Таблицы символов ABI} + +Запись MELF64 в таблице символов имеет размер 24 байта. Символ 0 --- полностью нулевая запись неопределённого символа. В \code{st_info} привязка (binding) хранится в старшей тетраде, тип --- в младшей. + +Значения binding: LOCAL=0, GLOBAL=1, WEAK=2. Типы символов: NOTYPE, OBJECT, FUNC, SECTION, FILE. Для обычных символов времени запуска используется visibility DEFAULT; в подстановке символов при запуске (interposition) участвуют только имена DEFAULT GLOBAL/WEAK. + +\subsection{st\_value в ET\_REL} + +Для определённого символа \code{st_shndx} содержит индекс секции, а \code{st_value} --- байтовое смещение от её начала. Для неопределённого символа используется \code{SHN_UNDEF} и нулевое значение. Директива \code{.equ} остаётся механизмом абсолютных выражений времени ассемблирования и не обязана создавать запись MELF symbol table. + +\subsection{Статическое разрешение символов} + +LOCAL никогда не разрешается через границу object file. Сильный GLOBAL имеет приоритет над WEAK. Несколько сильных GLOBAL-определений являются ошибкой. Если сильное определение отсутствует, несколько WEAK-определений выбираются детерминированно по порядку входных файлов компоновщика. Неопределённый сильный GLOBAL после окончательной компоновки является ошибкой. При \code{mcpu-ld -r} такой символ может оставаться неопределённым и переносится вместе с RELA в новый ET_REL. + +\subsection{Lifecycle-символы} + +\code{_start} и \code{_life} обязательны как GLOBAL/FUNC для каждого исполняемого файла; \code{_fini} --- необязательный GLOBAL/FUNC. Lifecycle-символы не могут быть WEAK. Псевдометка исходного текста \code{_start_life} существует только в синтаксисе ассемблера и эквивалентна двум символам \code{_start} и \code{_life} с одним смещением в секции \code{.text}, когда \code{.init} отсутствует. + +\section{RELA ABI} + +Запись MELF64 RELA имеет размер 24 байта: \code{r_offset}, \code{r_info} и знаковый 64-битный \code{r_addend}. Базовое выражение relocation: \code{S + A}. Все текущие MCPU relocation исправляют ровно 8 байт. + +\begin{longtable}{@{}L{38mm}L{28mm}L{30mm}L{58mm}@{}} +\toprule +\textbf{Relocation} & \textbf{Где находится поле} & \textbf{Цель} & \textbf{Записываемое значение}\\ +\midrule +\endfirsthead +\toprule +\textbf{Relocation} & \textbf{Где находится поле} & \textbf{Цель} & \textbf{Записываемое значение}\\ +\midrule +\endhead +R\_MCPU\_CODE64 & PROGRAM & PROGRAM & окончательное необработанное байтовое смещение PROGRAM\\ +R\_MCPU\_DATA64 & PROGRAM & DATA/.bss & окончательное необработанное байтовое смещение DATA\\ +R\_MCPU\_PTR64\_CODE & PROGRAM & загружаемый сегмент & PTR64(segment,offset) в порядке байтов машинного кода\\ +R\_MCPU\_PTR64\_DATA & секция данных & загружаемый сегмент & PTR64 в представлении данных целевой архитектуры\\ +R\_MCPU\_CPTR64\_DATA & секция данных & только PROGRAM & PTR64(PROGRAM,offset)\\ +\bottomrule +\end{longtable} + +В \code{ET_REL} поле \code{r_offset} является байтовым смещением внутри целевой секции, указанной через \code{sh_info}; \code{sh_link} указывает на таблицу символов. Все 8 байт исправляемого поля обязаны помещаться в сохранённой секции. Relocation непосредственно в область NOBITS запрещена: в файле нет байтов, которые можно исправить. + +Relocation неопределённого WEAK-указателя с нулевым addend может дать канонический полностью нулевой NULL. Для необработанных CODE64/DATA64 такое молчаливое преобразование в zero запрещено, потому что offset 0 является допустимым архитектурным адресом. + + +\section{Частичная компоновка mcpu-ld -r} + +Режим \code{mcpu-ld -r} объединяет несколько MELF \code{ET_REL} в один новый \code{ET_REL}. Это именно \term{partial link}{частичная компоновка}, а не построение исполняемого образа. + +Для каждой одноимённой секции вклады входных объектов конкатенируются в порядке входа с соблюдением их \code{sh_addralign}. Значения определённых символов и \code{r_offset} пересчитываются в координаты объединённой секции. LOCAL-символы разных объектов остаются раздельными даже при одинаковом имени. Для GLOBAL/WEAK действует обычное правило силы: сильный GLOBAL заменяет WEAK; два сильных GLOBAL одного имени являются ошибкой. + +RELA при \code{-r} не превращаются в окончательные архитектурные адреса. Они переносятся в выходной объект с новым section-relative offset и ссылкой на канонический символ результирующего ET_REL. Неопределённые GLOBAL/WEAK допускаются и могут быть разрешены последующей окончательной компоновкой. + +\begin{McpuCommand} +mcpu-ld -r -o combined.o a.o b.o c.o +mcpu-ld --stack-size=64K -o program main.o combined.o +\end{McpuCommand} + +STACK/HEAP, lifecycle-финализация, Program Headers, SONAME/NEEDED и runtime image в режиме \code{-r} не создаются. Поэтому \code{-r} взаимоисключён с \code{--dynamic}/\code{--shared}; опции размеров STACK/HEAP и \code{--soname} для него недопустимы. + +\section{Статические библиотеки lib*.a} + +Статическая библиотека MCPU не является новым типом MELF-файла. Это обычный UNIX \code{ar}-контейнер, чьи ordinary members являются независимыми текущими MELF \code{ET_REL}. Утилита \code{mcpu-ar} создаёт deterministic archives и при модификаторе \code{s} записывает настоящий System-V/GNU-style symbol index. + +\begin{McpuCommand} +mcpu-ar rcs libmodel.a model.o solver.o helpers.o +mcpu-ar t libmodel.a +\end{McpuCommand} + +При линковке из архива извлекается только member, определяющий текущий неразрешённый сильный GLOBAL. Неопределённый WEAK сам по себе member не вытягивает. После каждого извлечения текущий архив повторно сканируется до устойчивого состояния, поэтому новый member может породить ссылку на member, расположенный раньше в том же архиве. Уже пройденный отдельный \code{-l} archive автоматически после следующей библиотеки не пересматривается; повторное \code{-la} имеет реальный смысл. + +Для ABI-v1 и для \code{-r} \code{-lfoo} ищет только \code{libfoo.a}. Для ABI-v3 \code{--dynamic}/\code{--shared} поиск является directory-major: в каждом каталоге сначала проверяется \code{libfoo.so}, затем \code{libfoo.a}. Статически извлечённые members становятся частью результирующего MELF и не создают \code{DT_NEEDED}. + +\section{Метаданные профиля целевой архитектуры} + +\code{.mcpu.target} обязательна в \code{ET_REL} и \code{ET_EXEC}; модули ABI v2/v3 несут тот же профиль совместимости. Запись имеет размер 64 байта, не является ALLOC и выровнена на 8. + +\begin{longtable}{@{}L{38mm}L{18mm}L{94mm}@{}} +\toprule +\textbf{Поле} & \textbf{Смещение} & \textbf{Смысл}\\ +\midrule +\endfirsthead +\toprule +\textbf{Поле} & \textbf{Смещение} & \textbf{Смысл}\\ +\midrule +\endhead +mt\_version & 0 & версия структуры профиля, сейчас 1\\ +mt\_size & 4 & размер записи, сейчас 64\\ +mt\_isa\_revision & 8 & ревизия ISA, сейчас 1\\ +mt\_abi\_revision & 12 & ревизия программного ABI, сейчас 1\\ +mt\_program\_model & 16 & профиль ILP32/LP64/LLP64\\ +mt\_data\_byte\_order & 20 & порядок байтов объектов данных; согласован с EI\_DATA\\ +mt\_data\_word\_order & 24 & порядок слов LibMPU\\ +mt\_integer\_model & 28 & дополнительный код (дополнительный код)\\ +mt\_address\_model & 32 & PTR64 SEG4:OFF60\\ +mt\_register\_model & 36 & REG24 / регистровый пул 512 KiB\\ +mt\_real\_model & 40 & LibMPU\\ +mt\_complex\_model & 44 & LibMPU\\ +mt\_max\_align & 48 & максимальное обычное выравнивание ABI = 16\\ +mt\_flags & 52 & пока 0\\ +mt\_required\_features & 56 & пока 0\\ +\bottomrule +\end{longtable} + +Компоновщик требует совместимости профилей целевой архитектуры. Загрузчик повторно проверяет окончательный профиль до создания guest-областей памяти. Среда выполнения не исправляет несовместимый профиль и не конвертирует порядок байтов данных автоматически. + +\section{Метаданные lifecycle} + +Окончательный \code{ET_EXEC} содержит \code{.mcpu.lifecycle} фиксированного размера 32 байта. Запись описывает \textbf{семантику жизненного цикла}, но не дублирует адреса точек входа: единственным нормативным источником адресов остаётся Ehdr. + +Флаги ABI v1 фиксируют: START обязателен, LIFE обязателен, INIT и FINI необязательны; \code{e_entry} указывает на START; без INIT START совпадает с LIFE; LIFE завершается END и может быть продолжен; штатный EXIT переходит в FINI; область запуска (startup area) остаётся действительной всё время жизни программы. + +\begin{normative} +Метаданные lifecycle намеренно не содержат копий указателей на \code{_start/_life/_fini}. Благодаря этому в образе нет двух независимо перемещаемых источников истины. Загрузчик проверяет согласованность Ehdr и записи lifecycle, после чего использует числовые поля точек входа из Ehdr. +\end{normative} + +\section{Таблица заголовков сегментов (Program Headers)} + +Запись MELF64 Phdr имеет размер 64 байта и расширяет привычную ELF-подобную форму полем \code{p_maxsz}. \code{p_paddr=0}: отдельного пространства физических адресов у текущего MCPU ABI нет. \code{p_align} относится к выравниванию guest-адреса и файла, а не к размеру страницы host-системы. + +\begin{center} +\begin{tabularx}{0.96\textwidth}{@{}L{28mm}L{34mm}L{24mm}Y@{}} +\toprule +\textbf{Пространство} & \textbf{p\_type} & \textbf{Флаги} & \textbf{Правило размеров}\\ +\midrule +PROGRAM & PT\_LOAD & R|X & filesz=memsz=maxsz\\ +DATA & PT\_LOAD & R|W & filesz <= memsz; maxsz=memsz\\ +CONTROL & PT\_LOAD & R & сохранённый и runtime-размеры равны\\ +INPUT & PT\_LOAD & R & сохранённый и runtime-размеры равны\\ +OUTPUT & PT\_LOAD & R|W & сохранённый и runtime-размеры равны\\ +TRACE & PT\_LOAD & R|W & сохранённый и runtime-размеры равны\\ +STACK & PT\_MCPU\_STACK & R|W & filesz=0; memsz=maxsz\\ +HEAP & PT\_MCPU\_HEAP & R|W & filesz=0; memsz <= maxsz\\ +\bottomrule +\end{tabularx} +\end{center} + +Для пространств, загружаемых из образа, \code{p_vaddr=PTR64(segment,0)} в координатах окончательного образа исполняемого файла или модуля. Дескриптор STACK обязателен в \code{ET_EXEC}; дескриптор HEAP необязателен. Округление host-mmap к размеру страницы не является частью ABI. + +\section{Статический ET\_EXEC ABI v1} + +Обычный \code{mcpu-ld} завершает всю работу времени компоновки до запуска: +\begin{enumerate} +\item проверяет профиль целевой архитектуры каждого \code{ET_REL}; +\item размещает вклады секций с учётом \code{sh_addralign}; +\item создаёт единственную 16-байтовую защитную нулевую область DATA (NULL guard) по смещениям 0..15; +\item разрешает символы GLOBAL/WEAK/UNDEF; +\item применяет все RELA с проверкой диапазона и сегмента; +\item проверяет обязательные определения lifecycle; +\item назначает окончательные PTR64-адреса секций и символов; +\item формирует окончательные заголовков сегментов (Program Headers) и метаданные. +\end{enumerate} + +Исполняемый файл ABI v1 не нуждается в поиске символов при запуске. Любая попытка использовать \code{--ldpreload} или \code{MCPU_LD_PRELOAD} с ABI v1 является ошибкой, а не поводом неявно превратить программу в dynamic ABI. + +\section{Точки входа lifecycle исполняемого файла} + +\subsection{С секцией .init} +\code{_start} принадлежит \code{.init}; поля \code{e_entry} и \code{e_init_entry} указывают на \code{_start}. \code{_life} принадлежит \code{.text}, а \code{e_life_entry} указывает на \code{_life}. После \code{END} в INIT супервизор начинает повторяющиеся фазы LIFE. + +\subsection{Без секции .init} +\code{_start} и \code{_life} обязаны находиться по одному адресу в \code{.text}. Псевдометка \code{_start_life:} создаёт именно эту пару символов. В окончательном исполняемом файле \code{e_init_entry=0} и \code{e_entry == e_life_entry}. + +\subsection{FINI} +Если существует \code{.fini}/\code{_fini}, поле \code{e_fini_entry} указывает на него; иначе оно равно 0. Штатный \code{EXIT} после LIFE входит в FINI. Диагностическая остановка по внешнему лимиту шагов не имитирует \code{EXIT} и не обязана запускать FINI. + +\subsection{Связь MELF lifecycle с HLL Runtime ABI} +Сам MELF container определяет машинные entry points \code{_start}, \code{_life} и optional \code{_fini}. Поверх этого container contract существующий \code{mcpu-ld} реализует HLL complete-program layer: strong GLOBAL/FUNC \code{main+fini} в одном defining ET_REL классифицирует PROGRAM, а \code{init+life+fini} --- MODEL. Linker автоматически добавляет соответствующий CRT из \code{<MCPU-root>/lib/mcpu/}; отсутствие обязательного CRT является fatal error. Raw low-level objects с собственными \code{_start/_life[/_fini]} продолжают линковаться без automatic CRT, но смешивать оба режима запрещено. + +\begin{normative} +Это не меняет wire format MELF: HLL names являются linker policy над обычной symbol table, а окончательный ET_EXEC по-прежнему исполняется через нормативные lifecycle entry points. Loader не классифицирует HLL source form. +\end{normative} + +Полный контракт HLL + CRT + MCPU Libc, включая \code{McpuStartup}, PRE_RUNTIME/RUNNING, internal Libc hooks и MODEL return namespace, приведён в \code{libmcpu-bytecode-architecture-ru.pdf}. + +\section{Постоянная область запуска McpuStartup} + +До первой guest-инструкции загрузчик строит в верхней части STACK структуру \code{McpuStartup}. Она имеет поле версии, поэтому имя C-типа намеренно не содержит суффикс V1. + +\begin{center} +\begin{tabularx}{0.88\textwidth}{@{}L{20mm}L{30mm}L{18mm}Y@{}} +\toprule +\textbf{Смещение} & \textbf{Поле} & \textbf{Размер} & \textbf{Смысл}\\ +\midrule +0x00 & version & 4 & \code{MCPU_STARTUP_VERSION_1}\\ +0x04 & size & 4 & 32\\ +0x08 & argc & 8 & число аргументов guest-программы\\ +0x10 & argv & 8 & PTR64(STACK, массив argv)\\ +0x18 & зарезервировано & 8 & 0\\ +\bottomrule +\end{tabularx} +\end{center} + +После заголовка располагается массив \code{argv[argc+1]} из PTR64, завершающий NULL, затем строки аргументов, завершённые NUL, и нулевое дополнение. \code{startup_base} выровнен на 16 байт. Начальное состояние: \code{PC=e_entry}, \code{SP=startup_base}, \code{FP=stack_size}. Искусственного CALL-frame для \code{_start} нет. + +\section{Исторический MELF ABI v2} + +ABI v2 ввёл \code{ET_DYN}, \code{.dynsym/.dynstr} и сохранённые runtime RELA, но не имел SONAME/NEEDED. Полный набор shared objects задавался явным \code{mcpu-run --ldpreload=a.so:b.so}; вся startup-компоновка завершалась до первой инструкции \code{_start}. ABI v2 остаётся читаемым для совместимости, тогда как текущие \code{mcpu-ld --dynamic}/\code{--shared} выпускают ABI v3. + +\section{Frozen MELF ABI v3} + +ABI v3 сохраняет image/symbol/RELA-модель ABI v2 и добавляет нормальную модель зависимостей. Каждый dynamic ABI-v3 \code{ET_EXEC}/\code{ET_DYN} содержит ровно одну не-ALLOC секцию \code{.dynamic} типа \code{SHT_DYNAMIC}, связанную через \code{sh_link} с \code{.dynstr}. + +Запись \code{MElf64_Dyn} имеет размер 16 байт: знаковый 64-битный \code{d_tag} и беззнаковый 64-битный \code{d_val}. Текущие теги: + +\begin{center} +\begin{tabularx}{0.82\textwidth}{@{}L{42mm}L{22mm}Y@{}} +\toprule +\textbf{Тег} & \textbf{Значение} & \textbf{Смысл}\\ +\midrule +\code{DT_NULL} & 0 & обязательный единственный терминатор canonical stream\\ +\code{DT_NEEDED} & 1 & offset имени требуемого SONAME в \code{.dynstr}\\ +\code{DT_SONAME} & 14 & offset собственного SONAME shared object в \code{.dynstr}\\ +\bottomrule +\end{tabularx} +\end{center} + +После \code{DT_NULL} записей быть не должно. ABI-v3 \code{ET_DYN}, используемый как normal dependency, имеет ровно один SONAME. \code{ET_EXEC} не имеет собственного SONAME. Оба типа могут иметь ноль или более NEEDED в записанном порядке. + +\section{SONAME, NEEDED и link-time -L/-l} + +Имена development link, runtime identity и physical file разделены. Типичная установка: + +\begin{McpuCode} +libfoo.so -> libfoo.so.1 +libfoo.so.1 -> libfoo.so.1.2.3 +libfoo.so.1.2.3 # DT_SONAME = libfoo.so.1 +\end{McpuCode} + +При \code{-lfoo} linker находит development name \code{libfoo.so}, полностью валидирует выбранный ABI-v3 ET_DYN, читает его SONAME и записывает в потребитель именно \code{DT_NEEDED [libfoo.so.1]}. Абсолютный путь, имя symlink и physical filename в NEEDED не попадают. + +ABI-v3 dynamic output в каждом search directory предпочитает \code{libfoo.so}, затем \code{libfoo.a}. Если выбран archive, нужные ET_REL members встраиваются статически и никакого NEEDED для них нет. ABI-v1 и \code{-r} shared candidate не рассматривают. + +\section{Поиск библиотек и runtime root} + +\code{mcpu-ld} и \code{mcpu-run} используют общую relocatable root/config policy. Базовый system directory равен \code{<runtime-root>/lib}; конфигурационная цепочка в порядке роста приоритета: + +\begin{McpuCode} +<runtime-root>/etc/mcpu-ld.conf +/etc/mcpu/mcpu-ld.conf +$HOME/.mcpu/etc/mcpu-ld.conf +\end{McpuCode} + +Переменная конфигурации \code{MCPU_LD_SYSTEM_LIBRARY_PATH} заменяет system path; пустое значение отключает его. \code{--no-config} не читает config layers, но сохраняет executable-derived default. \code{--sys-root=PATH} подразумевает \code{--no-config} и использует \code{PATH/lib}. Для linker явные \code{-L} идут раньше system path; для runtime явный \code{--library-path} также имеет более высокий search priority. + +\section{Рекурсивная загрузка NEEDED} + +\code{mcpu-run} сначала читает прямые \code{DT_NEEDED} главного ET_EXEC, ищет каждый SONAME по runtime search policy, валидирует ABI-v3 ET_DYN и требует точного совпадения внутреннего \code{DT_SONAME} с запрошенным именем. Затем так же обрабатываются транзитивные NEEDED. + +Граф зависимостей строится breadth-first и дедуплицируется по SONAME. Direct dependencies предшествуют transitive; siblings сохраняют порядок NEEDED слева направо. Цикл вида \code{A -> B -> A} допустим и завершается через предварительную регистрацию SONAME. + +\section{.dynsym/.dynstr и runtime RELA} + +\code{.dynsym/.dynstr} являются частью startup-link ABI, в отличие от developer/debug \code{.symtab/.strtab}. DEFAULT GLOBAL/WEAK участвуют в выборе определения; LOCAL никогда не входит в interposition namespace. Lifecycle-имена \code{_start/_life/_fini} принадлежат главному executable и не являются runtime-preemptible symbols. + +Runtime \code{SHT_RELA} использует тот же \code{MElf64_Rela}. \code{sh_link} указывает на \code{.dynsym}, \code{sh_info} --- на allocated section с исправляемым полем, а \code{r_offset} остаётся offset относительно output section. Все runtime RELA выполняются до \code{_start}; lazy binding отсутствует. + +\section{Детерминированный runtime symbol scope} + +Для одного имени сначала сравнивается binding strength: GLOBAL всегда сильнее WEAK. Среди определений одинаковой силы действует scope: + +\begin{enumerate} +\item explicit preload modules --- наивысший scope, внутри списка правый элемент имеет больший приоритет; +\item главный ET_EXEC; +\item normal DT_NEEDED graph в breadth-first, left-to-right порядке. +\end{enumerate} + +Таким образом direct dependency выигрывает у конфликтующей transitive dependency той же силы, а первый sibling в NEEDED order выигрывает у следующего. Если GLOBAL нет вообще, тот же scope применяется к WEAK. + +\code{MCPU_LD_PRELOAD} является environment counterpart для \code{--ldpreload}. Environment entries добавляются первыми, command-line entries --- после них; существующее rightmost-wins правило поэтому естественно делает command line сильнее environment. Эта переменная не является частью \code{mcpu-ld.conf} и не подавляется \code{--no-config}. + +\section{Контракт ET_DYN} + +ABI-v2/v3 \code{ET_DYN} содержит только library \code{.text/.data/.bss} плюс non-ALLOC metadata. Он не владеет \code{.init}, \code{.fini}, \code{.control}, \code{.input}, \code{.output}, \code{.trace}, lifecycle definitions, \code{PT_MCPU_STACK} или \code{PT_MCPU_HEAP}. STACK, HEAP и model spaces принадлежат главному \code{ET_EXEC}. Для ABI v3 normal shared object SONAME обязателен. + +\section{LOCAL relocation внутри ET_DYN} + +LOCAL-символ остаётся частным и неподменяемым, но окончательная process base модуля заранее неизвестна. Поэтому relocation внутри ET_DYN, которой нужен окончательный PROGRAM/DATA address, сохраняется как LOCAL runtime RELA. Startup linker разрешает её строго внутри исходного модуля и добавляет runtime base соответствующего сегмента. Эта коррекция является нормативной частью runtime relocation contract и полностью сохраняется в ABI v3. + +\begin{center} +\begin{tikzpicture}[font=\sffamily\small,>=Stealth,node distance=7mm] +\node[draw=McpuBlue,rounded corners,fill=McpuBlueBg,minimum width=46mm] (so) {ET\_DYN после mcpu-ld}; +\node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,below=of so,minimum width=62mm] (rel) {LOCAL runtime RELA + module-relative symbol}; +\node[draw=McpuGreen,rounded corners,fill=McpuGreenBg,below=of rel,minimum width=56mm] (run) {startup linker в mcpu-run}; +\node[draw=McpuBlue,rounded corners,fill=McpuGrayBg,below=of run,minimum width=62mm] (final) {process PTR64 с PROGRAM/DATA base}; +\draw[->,thick] (so)--(rel); +\draw[->,thick] (rel)--(run); +\draw[->,thick] (run)--(final); +\end{tikzpicture} +\end{center} + +\section{Размещение образа процесса и единственный DATA guard} + +У MCPU нет единого ELF-подобного смещения загрузки (load bias): PTR64 имеет независимые селекторы сегментов. Компоновщик запуска назначает отдельную базу для каждого архитектурного сегмента. + +\begin{center} +\begin{tabularx}{0.9\textwidth}{@{}L{48mm}Y@{}} +\toprule +\textbf{Вклад} & \textbf{База}\\ +\midrule +main PROGRAM & 0\\ +lib1 PROGRAM & \code{align_up(main_program_end, lib1_align)}\\ +lib2 PROGRAM & \code{align_up(lib1_program_end, lib2_align)}\\ +main DATA & 0\\ +первая DATA-библиотека & после окончательного DATA main; минимум 16, если DATA main отсутствует\\ +\bottomrule +\end{tabularx} +\end{center} + +В \code{ET_DYN} нет собственного DATA NULL guard. Во всём образе процесса существует ровно одна нулевая область offsets 0..15. Поэтому локальный символ библиотеки с относительным к модулю DATA offset 0 после размещения при запуске закономерно получает process offset 16 или больше. + +\section{BSS и соотношение filesz/memsz} + +\code{.bss} имеет тип \code{SHT_NOBITS}. Она участвует в расчёте адресов и увеличивает DATA \code{p_memsz}, но не увеличивает объём данных в файле \code{p_filesz}. Статический и startup-загрузчики сначала получают изначально обнулённую память, затем копируют сохранённые байты, поэтому NOBITS детерминированно начинается с нулей. + +Для PROGRAM хранится только машинный код, поэтому \code{filesz=memsz}. Для DATA обычно \code{filesz < memsz}. LOCAL-ссылки из \code{ET_DYN} в DATA/.bss всё равно требуют перебазирования по базе модуля, даже если целевые байты относятся к NOBITS. + +\section{Дескриптор STACK и командная строка} + +Размер STACK является частью ABI исполняемого файла. Он задаётся компоновщиком через \code{--stack-size=N} и записывается в \code{PT_MCPU_STACK}; \code{mcpu-run} не может его переопределить. Разделяемые объекты ABI v2 не задают политику STACK. + +Предпочтительный синтаксис запуска использует явный разделитель: +\begin{lstlisting}[language={}] +mcpu-run [runtime-options] -- executable [program-args...] +\end{lstlisting} + +Все параметры после \code{--} передаются в guest \code{argv} без повторного разбора программой \code{mcpu-run}; текстовое имя исполняемого файла становится \code{argv[0]}. Старая форма без разделителя сохранена для совместимости. + +\section{Дескриптор HEAP, _end и SYS\_brk} + +Необязательный \code{PT_MCPU_HEAP} хранит начальный подготовленный размер в \code{p_memsz} и неизменяемый предел в \code{p_maxsz}; оба значения не меньше 16, причём max >= initial. Определяемый компоновщиком символ \code{_end=PTR64(HEAP,16)} является частным для исполняемого файла, неподменяемым и задаёт начальный и минимальный program break. + +\code{SYS_brk} имеет номер 10. NULL-аргумент запрашивает текущее значение; HEAP PTR64 задаёт абсолютный новый break. В отличие от общего stable syscall rule \code{-MCPU_errno}, \code{SYS_brk} возвращает фактический текущий HEAP PTR64. При отклонённом запросе break не меняется. Shrink зануляет освобождённый диапазон, но не обязан уменьшать host-отображение памяти; последующий рост остаётся детерминированным. Guest PTR64 остаётся корректным при перемещении host backing, поскольку содержит segment+offset, а не host-адрес. + +\section{Дисциплина проверки загрузчика} + +Загрузчик сначала полностью проверяет структуру файла и только затем создаёт guest-области памяти. В частности проверяются: +\begin{itemize} +\item сигнатура, класс, порядок данных, версия, OSABI, ABI/machine, размеры заголовков и границы таблиц; +\item обязательные метаданные target/lifecycle и зарезервированные поля; +\item уникальность заголовков сегментов (Program Headers), права доступа, соотношения размеров, выравнивание и границы OFF60; +\item соответствие каждой ALLOC-секции её архитектурному сегменту; +\item selector/range точек входа lifecycle; +\item нулевые байты DATA guard в статическом исполняемом образе; +\item для ABI v2/v3 --- структура dynsym/RELA, диапазоны символов, границы исправляемых полей и семантика целевого сегмента; для ABI v3 дополнительно canonical .dynamic, SONAME/NEEDED и соответствие identity загруженного ET_DYN запрошенному SONAME. +\end{itemize} + +\begin{principle} +\textbf{Загрузчик проверяет, но не «чинит».} Он не выводит lifecycle из названий секций, не создаёт недостающий guard, не меняет порядок байтов PROGRAM, не увеличивает STACK, не ослабляет HEAP limit и не переинтерпретирует несовместимый профиль целевой архитектуры. Компоновщик запуска ABI v2/v3 выполняет только явно определённые сохранённые relocation, dependency discovery и разрешение символов. +\end{principle} + +\section{Режимы команды mcpu-ld} + +\begin{lstlisting}[language={}] +# partial ET_REL +mcpu-ld -r -o combined.o a.o b.o + +# статический ABI v1 +mcpu-ld --stack-size=4096 --heap-size=64K --heap-limit=1M \ + -L$HOME/.mcpu/lib -lmodel -o app main.o + +# главный исполняемый файл frozen ABI v3 +mcpu-ld --dynamic --stack-size=64K --no-heap \ + -L$HOME/.mcpu/lib -lrk4 -o model pendulum.o + +# shared object ABI v3 +mcpu-ld --shared --soname=librk4.so.1 \ + -o librk4.so.1.0.0 rk4.o +\end{lstlisting} + +STACK/HEAP принадлежат только \code{ET_EXEC}. \code{--soname} допустим только для \code{--shared}. \code{-r} не создаёт executable/runtime metadata и использует для \code{-l} только static archives. Суффиксы размера K/KiB, M/MiB, G/GiB используют двоичные множители. + + +\section{Инструменты анализа и безопасного редактирования MELF} +Binary utilities работают поверх того же MELF contract и не создают отдельного container ABI. + +\subsection{Read-only inspection} +\code{mcpu-readelf} показывает file/program/section headers, symbol tables, RELA, \code{.dynamic} и MCPU-specific metadata. \code{mcpu-nm} выводит \code{.symtab} или \code{.dynsym} и умеет читать static archives. \code{mcpu-objdump} объединяет section/relocation views с instruction disassembly; decoder общий с CPU runtime через \code{__cpu_decode_at()}, поэтому ISA не дублируется отдельным parser-ом. + +\subsection{mcpu-strip} +Текущая policy намеренно консервативна. В ET\_REL ordinary \code{.symtab/.strtab} сохраняются, потому что они являются частью последующей link semantics. В ET\_EXEC/ET\_DYN ordinary non-ALLOC \code{SHT_SYMTAB} может быть удалена только если retained sections от неё не зависят; \code{.dynsym/.dynstr/.dynamic}, RELA, MCPU metadata и allocated image всегда сохраняются. Современные final-image writers уже компактны, поэтому для обычного ET\_EXEC/ET\_DYN strip часто является byte-for-byte no-op. + +\subsection{mcpu-objcopy} +\code{mcpu-objcopy} выполняет только MELF-to-MELF structural copy/edit. Safe interface умеет выбирать/удалять sections и dump/add/update/rename для metadata, но запрещает разрушать allocated image, frozen dynamic metadata, runtime relocations и ET\_REL link-critical dependencies. Новая section через \code{--add-section} является non-ALLOC \code{SHT_PROGBITS}. Metadata tail может быть repacked, но program headers и runtime-visible PT\_LOAD bytes должны оставаться неизменными. + +Flat binary, Intel HEX, S-record, VMA/LMA и firmware-image conversion намеренно отсутствуют: виртуальная MCPU исполняет MELF напрямую. + +\subsection{Non-ALLOC debug metadata и frozen ABI v3} +\begin{normative} +Будущий debug format не обязан менять MELF ABI v3, если его sections остаются non-ALLOC, не входят в PT\_LOAD, не требуются loader/runtime для исполнения и могут быть полностью удалены без изменения guest-visible поведения. Версия такого debug format должна быть самостоятельной и не кодироваться повышением \code{EI_ABIVERSION} MELF. +\end{normative} + +В ET\_REL debug sections при необходимости могут иметь link-time RELA к code/data symbols; такие relocations являются metadata link-editor contract и не должны становиться обязательными runtime relocations final ET\_EXEC/ET\_DYN. + + +\section{Правила совместимости} + +\begin{enumerate} +\item Статический \code{ET_EXEC} ABI v1 и \code{ET_REL} ABI v1 остаются основой статического/разработческого пути; ABI-v1 executable отвергает preload. +\item Исторический ABI v2 остаётся читаемым и использует explicit-preload startup model без SONAME/NEEDED. +\item Frozen ABI v3 является текущим normal dynamic ABI. Несовместимое изменение \code{.dynamic}, NEEDED/SONAME, runtime scope или retained-relocation contract требует нового номера ABI. +\item Static \code{lib*.a} и \code{mcpu-ld -r} являются link-time механизмами над ABI-v1 ET_REL и не требуют ABI v4. +\item Ревизия ISA и CALL ABI остаются равны 1 независимо от версии контейнерного dynamic ABI. +\item Будущие расширения должны использовать явные version/feature поля, а не менять смысл зарезервированных битов или записей молча. Non-ALLOC debug metadata с самостоятельной версией формата не требует нового MELF ABI, пока loader/runtime semantics не зависят от неё. +\end{enumerate} + + +\appendix + +\section{Размеры сериализованных записей} + +\begin{center} +\begin{tabularx}{0.9\textwidth}{@{}L{48mm}L{22mm}Y@{}} +\toprule +\textbf{Запись} & \textbf{Байт} & \textbf{Назначение}\\ +\midrule +MElf64\_Ehdr & 88 & заголовок файла\\ +MElf64\_Shdr & 64 & заголовок секции\\ +MElf64\_Sym & 24 & запись таблицы символов\\ +MElf64\_Rela & 24 & запись RELA\\ +MElf64\_Dyn & 16 & запись \code{.dynamic} ABI v3\\ +MElf64\_Phdr & 64 & заголовок сегмента (Program Header) с \code{p_maxsz}\\ +MElf\_McpuTarget & 64 & метаданные профиля целевой архитектуры\\ +MElf\_McpuLifecycle & 32 & метаданные lifecycle\\ +McpuStartup & 32 & заголовок области запуска (startup) в STACK guest-программы; это не сериализованные метаданные MELF\\ +\bottomrule +\end{tabularx} +\end{center} + +\section{Канонические имена секций} + +\begin{center} +\begin{tabularx}{\textwidth}{@{}L{62mm}Y@{}} +\code{.init}, \code{.text}, \code{.fini} & Секции машинного кода PROGRAM.\\ +\code{.data}, \code{.bss} & Объекты DATA; \code{.bss} имеет тип NOBITS.\\ +\code{.control}, \code{.input}, \code{.output}, \code{.trace} & Модельные пространства, принадлежащие ET\_EXEC.\\ +\code{.symtab}, \code{.strtab} & Информация о символах для разработки и статической компоновки.\\ +\code{.dynsym}, \code{.dynstr} & Runtime symbol/string tables ABI v2/v3.\\ +\code{.dynamic} & Non-ALLOC dependency metadata ABI v3: SONAME/NEEDED/NULL.\\ +\code{.rela.init}, \code{.rela.text}, \code{.rela.fini} & Секции relocation для PROGRAM.\\ +RELA секций данных & \code{.rela.data}, \code{.rela.control}, \code{.rela.input}, \code{.rela.output}, \code{.rela.trace}.\\ +\code{.mcpu.target} & Обязательный профиль целевой архитектуры.\\ +\code{.mcpu.lifecycle} & Окончательный lifecycle-контракт исполняемого файла.\\ +\code{.shstrtab} & Таблица строк с именами секций.\\ +\end{tabularx} +\end{center} + +\section{Нормативная памятка frozen dynamic ABI v3} + +\begin{normative} +\begin{itemize} +\item New dynamic output использует ABI v3; ABI v2 поддерживается как исторический explicit-preload input format. +\item ABI-v3 ET_DYN имеет обязательный SONAME и не владеет lifecycle, model spaces, STACK или HEAP. +\item ABI-v3 ET_EXEC/ET_DYN имеют canonical non-ALLOC \code{.dynamic}; NEEDED содержит SONAME, а не pathname или development symlink name. +\item Normal dependencies обнаруживаются рекурсивно по SONAME, deduplicate/cycle handling выполняются до guest execution. +\item GLOBAL сильнее WEAK; explicit preload имеет наивысший equal-strength scope и rightmost priority; затем main executable; затем breadth-first/left-to-right NEEDED graph. +\item LOCAL никогда не участвует в name interposition; нуждающиеся в process base LOCAL ET_DYN relocations сохраняются и rebased внутри исходного модуля. +\item Все runtime relocation и symbol resolution заканчиваются до \code{_start}; PLT/GOT, lazy binding, \code{dlopen()}, \code{dlsym()} и \code{dlclose()} отсутствуют. +\item Во всём process DATA существует один NULL guard; отдельный ET_DYN guard не создаётся. +\item Static archives и \code{mcpu-ld -r} работают на link-time поверх ET_REL и не меняют frozen ABI v3. +\end{itemize} +\end{normative} + +\end{document} diff --git a/doc/src/libmcpu-opcode-table.tex b/doc/src/libmcpu-opcode-table.tex new file mode 100644 index 0000000..33d0bc4 --- /dev/null +++ b/doc/src/libmcpu-opcode-table.tex @@ -0,0 +1,174 @@ +\begin{longtable}{@{}L{20mm}L{19mm}L{43mm}L{77mm}@{}} +\toprule +\textbf{Mnemonic} & \textbf{OPCODE16} & \textbf{Operands} & \textbf{Назначение} \\ +\midrule +\endfirsthead +\toprule +\textbf{Mnemonic} & \textbf{OPCODE16} & \textbf{Operands} & \textbf{Назначение} \\ +\midrule +\endhead +NOP & \code{0x0000} & \code{---} & Нет операции; PC переходит к следующей инструкции. \\ +HALT & \code{0x0001} & \code{---} & Устанавливает machine halt state; используется как низкоуровневая остановка CPU. \\ +END & \code{0x0002} & \code{---} & Завершает текущую externally managed INIT/LIFE/FINI phase и возвращает управление supervisor-у. \\ +EXIT & \code{0x0003} & \code{r32 status} & Graceful завершение программы/модели с signed int32 status; при наличии FINI runtime переходит к _fini. \\ +SYSCALL & \code{0x0004} & \code{---} & Выполняет системный вызов через фиксированную register ABI area. \\ +SETF & \code{0x0010} & \code{PSWBIT8} & Устанавливает ровно один writable arithmetic/status flag в диапазоне PSW[15:0]; HF/TF/IF недопустимы. \\ +CLRF & \code{0x0011} & \code{PSWBIT8} & Очищает ровно один writable arithmetic/status flag в диапазоне PSW[15:0]; HF/TF/IF недопустимы. \\ +CLRCPUF & \code{0x0012} & \code{---} & Очищает CPU flag byte PSW[7:0], сохраняя PSW[15:8] и control/execution state. \\ +CLRMPUF & \code{0x0013} & \code{---} & Очищает MPU flag byte PSW[15:8], сохраняя PSW[7:0] и control/execution state. \\ +MOV & \code{0x0100} & \code{Rdst, Rsrc} & Копирует объект между регистрами одинаковой физической ширины. \\ +CLR & \code{0x0101} & \code{Rdst} & Заполняет выбранный регистр нулями. \\ +SWAP & \code{0x0102} & \code{Ra, Rb} & Обменивает содержимое двух регистров одинаковой ширины. \\ +BSWAP & \code{0x0103} & \code{Rdst, Rsrc} & Разворачивает порядок байтов source object; raw byte reversal без числовой интерпретации. \\ +LDI & \code{0x0110} & \code{Rdst, imm64} & Загружает signed integer immediate с приведением к ширине integer register; conversion PSW-neutral. \\ +LDR & \code{0x0111} & \code{Rdst, real literal} & Загружает exactly representable immediate Real32/Real64; более широкие constants следует хранить в DATA. \\ +LDC & \code{0x0112} & \code{Cdst, complex literal} & Загружает immediate Complex64 как две точно представимые Real32 components. \\ +LOAD & \code{0x0120} & \code{Rdst, DATA64} & Копирует объект из DATA по прямому DATA-relative адресу в регистр. \\ +STORE & \code{0x0121} & \code{DATA64, Rsrc} & Копирует регистр в DATA по прямому DATA-relative адресу. \\ +LEA & \code{0x0130} & \code{r64 dst, PTR64} & Формирует полный segment-aware PTR64 на label/target object. \\ +LEASP & \code{0x0131} & \code{r64 dst, OFF64} & Формирует STACK PTR64 как SP плюс signed byte offset. \\ +LOADP & \code{0x0132} & \code{R/C dst, r64 ptr} & Читает объект через PTR64 после проверки segment rights и bounds. \\ +STOREP & \code{0x0133} & \code{r64 ptr, R/C src} & Записывает объект через PTR64 после проверки segment rights и bounds. \\ +PADD & \code{0x0140} & \code{r64 dst, r64 ptr, r64 delta} & Прибавляет signed byte delta из r64 к PTR64 с сохранением segment selector. \\ +PADDI & \code{0x0141} & \code{r64 dst, r64 ptr, OFF64} & Прибавляет signed immediate byte offset к PTR64. \\ +PSUB & \code{0x0142} & \code{r64 dst, r64 ptr, r64 delta} & Вычитает signed byte delta из PTR64. \\ +PSUBI & \code{0x0143} & \code{r64 dst, r64 ptr, OFF64} & Вычитает signed immediate byte offset из PTR64. \\ +PDIFF & \code{0x0144} & \code{r64 dst, r64 a, r64 b} & Вычисляет signed разность byte offsets двух совместимых PTR64. \\ +BMOV & \code{0x0150} & \code{r64 dst, r64 src, r64 count} & Перемещает count байтов между PTR64 ranges с memmove-семантикой и полной предварительной проверкой. \\ +BMOVI & \code{0x0151} & \code{r64 dst, r64 src, IMM64 count} & То же, но byte count задан immediate. \\ +IADD & \code{0x1000} & \code{Idst, Ia, Ib} & Беззнаковое/битовое integer сложение выбранной ширины с публикацией integer flags. \\ +IADC & \code{0x1001} & \code{Idst, Ia, Ib} & Integer сложение с входным carry CF. \\ +ISUB & \code{0x1002} & \code{Idst, Ia, Ib} & Integer вычитание выбранной ширины. \\ +ISBB & \code{0x1003} & \code{Idst, Ia, Ib} & Integer вычитание с borrow/carry state. \\ +IAND & \code{0x1004} & \code{Idst, Ia, Ib} & Побитовое AND. \\ +IOR & \code{0x1005} & \code{Idst, Ia, Ib} & Побитовое OR. \\ +IXOR & \code{0x1006} & \code{Idst, Ia, Ib} & Побитовое XOR. \\ +INOT & \code{0x1010} & \code{Idst, Isrc} & Побитовое дополнение integer object. \\ +INEG & \code{0x1011} & \code{Idst, Isrc} & Двухкомплементарное отрицание integer object. \\ +ICMP & \code{0x1020} & \code{Ia, Ib} & Сравнивает integer operands одинаковой ширины и обновляет comparison flags без сохранения результата. \\ +ITEST & \code{0x1021} & \code{Ia, Ib} & Выполняет AND только для формирования integer condition flags. \\ +ISHL & \code{0x1030} & \code{Idst, Isrc} & Logical shift left на один bit. \\ +ISHR & \code{0x1031} & \code{Idst, Isrc} & Logical shift right на один bit. \\ +ISAL & \code{0x1032} & \code{Idst, Isrc} & Arithmetic shift left на один bit. \\ +ISAR & \code{0x1033} & \code{Idst, Isrc} & Arithmetic shift right на один bit с сохранением знака. \\ +IROL & \code{0x1034} & \code{Idst, Isrc} & Rotate left на один bit. \\ +IROR & \code{0x1035} & \code{Idst, Isrc} & Rotate right на один bit. \\ +IRCL & \code{0x1036} & \code{Idst, Isrc} & Rotate left through carry CF на один bit. \\ +IRCR & \code{0x1037} & \code{Idst, Isrc} & Rotate right through carry CF на один bit. \\ +ISHLN & \code{0x1040} & \code{Idst, Isrc, r64 n} & Logical shift left на n bits. \\ +ISHRN & \code{0x1041} & \code{Idst, Isrc, r64 n} & Logical shift right на n bits. \\ +ISALN & \code{0x1042} & \code{Idst, Isrc, r64 n} & Arithmetic shift left на n bits. \\ +ISARN & \code{0x1043} & \code{Idst, Isrc, r64 n} & Arithmetic shift right на n bits. \\ +IROLN & \code{0x1044} & \code{Idst, Isrc, r64 n} & Rotate left на n bits. \\ +IRORN & \code{0x1045} & \code{Idst, Isrc, r64 n} & Rotate right на n bits. \\ +IRCLN & \code{0x1046} & \code{Idst, Isrc, r64 n} & Rotate left through carry на n bits. \\ +IRCRN & \code{0x1047} & \code{Idst, Isrc, r64 n} & Rotate right through carry на n bits. \\ +IINC & \code{0x1050} & \code{Idst, Isrc} & Увеличивает integer value на единицу. \\ +IDEC & \code{0x1051} & \code{Idst, Isrc} & Уменьшает integer value на единицу. \\ +IXCHG & \code{0x1052} & \code{Ia, Ib} & Обменивает integer values одинаковой ширины. \\ +ICPY & \code{0x1053} & \code{Idst, Isrc} & Unsigned width-changing copy: zero-extension/truncation по integer object rules. \\ +ICVT & \code{0x1054} & \code{Idst, Isrc} & Signed width-changing conversion с sign extension/truncation; conversion PSW-neutral. \\ +IMUL & \code{0x1060} & \code{Iprod, Ia, Ib} & Полное unsigned integer произведение; destination имеет product width, допустимую ISA. \\ +ISMUL & \code{0x1061} & \code{Iprod, Ia, Ib} & Полное signed integer произведение. \\ +IDIV & \code{0x1062} & \code{Iquot, Irem, Inum, Iden} & Unsigned integer division с отдельными quotient и remainder. \\ +ISDIV & \code{0x1063} & \code{Iquot, Irem, Inum, Iden} & Signed integer division с отдельными quotient и remainder. \\ +IBITSLEN & \code{0x1064} & \code{r64 dst, Isrc} & Возвращает число значащих bits integer source. \\ +RADD & \code{0x2000} & \code{Rdst, Ra, Rb} & Сложение двух Real одинаковой physical width. \\ +RSUB & \code{0x2001} & \code{Rdst, Ra, Rb} & Вычитание Real. \\ +RMUL & \code{0x2002} & \code{Rdst, Ra, Rb} & Умножение Real. \\ +RDIV & \code{0x2003} & \code{Rdst, Ra, Rb} & Деление Real. \\ +RSQRT & \code{0x2010} & \code{Rdst, Rsrc} & Квадратный корень Real. \\ +RSIN & \code{0x2020} & \code{Rdst, Rsrc} & Синус Real. \\ +RCOS & \code{0x2021} & \code{Rdst, Rsrc} & Косинус Real. \\ +RTAN & \code{0x2022} & \code{Rdst, Rsrc} & Тангенс Real. \\ +RASIN & \code{0x2023} & \code{Rdst, Rsrc} & Арксинус Real. \\ +RACOS & \code{0x2024} & \code{Rdst, Rsrc} & Арккосинус Real. \\ +RATAN & \code{0x2025} & \code{Rdst, Rsrc} & Арктангенс Real. \\ +RLOG & \code{0x2030} & \code{Rdst, Rsrc} & Натуральный логарифм Real. \\ +RLOG10 & \code{0x2031} & \code{Rdst, Rsrc} & Десятичный логарифм Real. \\ +RLOG2 & \code{0x2032} & \code{Rdst, Rsrc} & Двоичный логарифм Real. \\ +REXP & \code{0x2033} & \code{Rdst, Rsrc} & Экспонента $e^x$. \\ +RSINH & \code{0x2040} & \code{Rdst, Rsrc} & Гиперболический синус. \\ +RCOSH & \code{0x2041} & \code{Rdst, Rsrc} & Гиперболический косинус. \\ +RTANH & \code{0x2042} & \code{Rdst, Rsrc} & Гиперболический тангенс. \\ +RPOW & \code{0x2050} & \code{Rdst, Ra, Rb} & Возводит Real a в степень Real b. \\ +RHYPOT & \code{0x2051} & \code{Rdst, Ra, Rb} & Вычисляет hypot(a,b) в Real domain. \\ +RATAN2 & \code{0x2052} & \code{Rdst, Ra, Rb} & Вычисляет atan2(y,x) в Real domain. \\ +RNEG & \code{0x2060} & \code{Rdst, Rsrc} & Изменяет знак Real. \\ +RABS & \code{0x2061} & \code{Rdst, Rsrc} & Модуль Real. \\ +RCMP & \code{0x2062} & \code{r64 dst, Ra, Rb} & Сравнивает Real a/b и возвращает classification \{-2,-1,0,+1\} в r64. \\ +RCPY & \code{0x2063} & \code{Rdst, Rsrc} & Копирует Real object совместимой physical width. \\ +RCVT & \code{0x2064} & \code{Rdst, Rsrc} & Преобразует Real между поддерживаемыми precision widths; conversion PSW-neutral. \\ +RFLOOR & \code{0x2070} & \code{Rdst, Rsrc} & Округляет Real вниз. \\ +RCEIL & \code{0x2071} & \code{Rdst, Rsrc} & Округляет Real вверх. \\ +RROUND & \code{0x2072} & \code{Rdst, Rsrc} & Округляет Real по правилам LibMPU. \\ +RFREXP & \code{0x2073} & \code{Rfrac, Iexp, Rsrc} & Разлагает Real на significand и integer exponent. \\ +RLDEXP & \code{0x2074} & \code{Rdst, Rsrc, Iexp} & Формирует Real как source, умноженный на степень двойки с integer exponent. \\ +RLOGB & \code{0x2075} & \code{Idst, Rsrc} & Возвращает integer exponent/classification для Real source. \\ +RMODF & \code{0x2076} & \code{Rfrac, Rint, Rsrc} & Разделяет Real на fractional и integral parts. \\ +RREMAIN & \code{0x2077} & \code{Rdst, Ra, Rb} & IEEE/LibMPU-style remainder для двух Real operands. \\ +RLOG1P & \code{0x2080} & \code{Rdst, Rsrc} & Вычисляет log(1+x) с Real semantics. \\ +REXPM1 & \code{0x2081} & \code{Rdst, Rsrc} & Вычисляет exp(x)-1. \\ +RASINH & \code{0x2082} & \code{Rdst, Rsrc} & Обратный гиперболический синус. \\ +RACOSH & \code{0x2083} & \code{Rdst, Rsrc} & Обратный гиперболический косинус. \\ +RATANH & \code{0x2084} & \code{Rdst, Rsrc} & Обратный гиперболический тангенс. \\ +ITOR & \code{0x2090} & \code{Rdst, Isrc} & Преобразует signed integer в Real; conversion PSW-neutral. \\ +UITOR & \code{0x2091} & \code{Rdst, Isrc} & Преобразует unsigned integer в Real; conversion PSW-neutral. \\ +RTOI & \code{0x2092} & \code{Idst, Rfrac, Rsrc} & Преобразует Real в signed integer destination и отдельно возвращает fractional remainder. \\ +RTOUI & \code{0x2093} & \code{Idst, Rfrac, Rsrc} & Преобразует Real в unsigned integer destination и отдельно возвращает fractional remainder. \\ +RISNEG & \code{0x20a0} & \code{r64 dst, Rsrc} & Возвращает r64 predicate: является ли Real отрицательным. \\ +RSIGN & \code{0x20a1} & \code{r64 dst, Rsrc} & Возвращает signed Real classification code \{-3..+1\} в r64. \\ +RISIND & \code{0x20a2} & \code{r64 dst, Rsrc} & Возвращает predicate для indeterminate Real representation. \\ +RISNAN & \code{0x20a3} & \code{r64 dst, Rsrc} & Возвращает predicate для NaN. \\ +RISNANMAX & \code{0x20a4} & \code{r64 dst, Rsrc} & Возвращает predicate для NaN max-class representation. \\ +RISNANMIN & \code{0x20a5} & \code{r64 dst, Rsrc} & Возвращает predicate для NaN min-class representation. \\ +RISINF & \code{0x20a6} & \code{r64 dst, Rsrc} & Возвращает predicate для infinity. \\ +RISSIGNULL & \code{0x20a7} & \code{r64 dst, Rsrc} & Возвращает predicate для signed null/zero classification. \\ +RCONST & \code{0x2100} & \code{Rdst, CONST8} & Загружает предопределённую математическую Real constant по CONST8 selector. \\ +CADD & \code{0x3000} & \code{Cdst, Ca, Cb} & Сложение Complex. \\ +CSUB & \code{0x3001} & \code{Cdst, Ca, Cb} & Вычитание Complex. \\ +CMUL & \code{0x3002} & \code{Cdst, Ca, Cb} & Умножение Complex. \\ +CDIV & \code{0x3003} & \code{Cdst, Ca, Cb} & Деление Complex. \\ +CEXP & \code{0x3020} & \code{Cdst, Csrc} & Комплексная экспонента. \\ +CSIN & \code{0x3021} & \code{Cdst, Csrc} & Комплексный синус. \\ +CCOS & \code{0x3022} & \code{Cdst, Csrc} & Комплексный косинус. \\ +CSINH & \code{0x3023} & \code{Cdst, Csrc} & Комплексный гиперболический синус. \\ +CCOSH & \code{0x3024} & \code{Cdst, Csrc} & Комплексный гиперболический косинус. \\ +CLOG & \code{0x3025} & \code{Cdst, Csrc} & Комплексный натуральный логарифм. \\ +CSQRT & \code{0x3026} & \code{Cdst, Csrc} & Комплексный квадратный корень. \\ +CPOW & \code{0x3050} & \code{Cdst, Ca, Cb} & Комплексное возведение в степень. \\ +CREAL & \code{0x3060} & \code{Rdst, Csrc} & Извлекает real component Complex object. \\ +CIMAG & \code{0x3061} & \code{Rdst, Csrc} & Извлекает imaginary component. \\ +CRTOC & \code{0x3062} & \code{Cdst, Rsrc} & Преобразует Real в Complex с нулевой imaginary component; conversion PSW-neutral. \\ +CGEN & \code{0x3063} & \code{Cdst, Rre, Rim} & Создаёт Complex из real и imaginary components. \\ +CPOLAR & \code{0x3064} & \code{Cdst, Rrho, Rtheta} & Создаёт Complex по polar representation rho/theta. \\ +CCONJ & \code{0x3065} & \code{Cdst, Csrc} & Комплексное сопряжение. \\ +CABS & \code{0x3066} & \code{Rdst, Csrc} & Возвращает абсолютную величину Complex. \\ +CNORM & \code{0x3067} & \code{Rdst, Csrc} & Возвращает квадрат нормы Complex. \\ +CARG & \code{0x3068} & \code{Rdst, Csrc} & Возвращает аргумент/phase Complex. \\ +CISEQ & \code{0x3070} & \code{r64 dst, Ca, Cb} & Возвращает predicate равенства двух Complex. \\ +CISNE & \code{0x3071} & \code{r64 dst, Ca, Cb} & Возвращает predicate неравенства двух Complex. \\ +CCPY & \code{0x3072} & \code{Cdst, Csrc} & Копирует Complex object совместимой physical width. \\ +CCVT & \code{0x3073} & \code{Cdst, Csrc} & Преобразует Complex между precision widths; conversion PSW-neutral. \\ +JMP & \code{0x4000} & \code{CODE64 target} & Безусловный прямой переход на PROGRAM address. \\ +JSET & \code{0x4001} & \code{PSWBIT8, CODE64} & Переходит, если выбранный branchable PSW bit установлен. \\ +JCLR & \code{0x4002} & \code{PSWBIT8, CODE64} & Переходит, если выбранный PSW bit очищен. \\ +JMPP & \code{0x4003} & \code{r64 ptr} & Косвенный переход через PROGRAM PTR64. \\ +CALL & \code{0x4010} & \code{CODE64 target} & Кладёт address следующей инструкции в STACK и передаёт управление target. \\ +RET & \code{0x4011} & \code{---} & Извлекает return PC из STACK и возвращается из CALL. \\ +CALLP & \code{0x4012} & \code{r64 ptr} & Косвенный CALL через PROGRAM PTR64 с push return PC. \\ +PUSH & \code{0x5000} & \code{R/C src} & Кладёт register object в STACK и изменяет SP согласно stack ABI. \\ +POP & \code{0x5001} & \code{R/C dst} & Извлекает register object из STACK. \\ +LDSP & \code{0x5010} & \code{R/C dst, OFF64} & Читает object по адресу SP + signed byte offset. \\ +STSP & \code{0x5011} & \code{OFF64, R/C src} & Записывает object по адресу SP + signed byte offset. \\ +ADJSP & \code{0x5020} & \code{OFF64} & Изменяет SP на signed byte offset с проверкой STACK bounds/alignment policy. \\ +PUSHFP & \code{0x5030} & \code{---} & Сохраняет текущий 64-bit FP в STACK. \\ +SETFP & \code{0x5031} & \code{---} & Устанавливает FP = SP. \\ +RESTSP & \code{0x5032} & \code{---} & Восстанавливает SP = FP. \\ +POPFP & \code{0x5033} & \code{---} & Извлекает 64-bit FP из STACK. \\ +LDFP & \code{0x5040} & \code{R/C dst, OFF64} & Читает object по адресу FP + signed byte offset. \\ +STFP & \code{0x5041} & \code{OFF64, R/C src} & Записывает object по адресу FP + signed byte offset. \\ +LEAFP & \code{0x5042} & \code{r64 dst, OFF64} & Формирует STACK PTR64 на FP + signed byte offset. \\ +SETSP & \code{0x5050} & \code{r64 ptr} & Устанавливает SP из валидного STACK PTR64. \\ +\bottomrule +\end{longtable} diff --git a/doc/src/libmcpu-psw-table.tex b/doc/src/libmcpu-psw-table.tex new file mode 100644 index 0000000..579e2d2 --- /dev/null +++ b/doc/src/libmcpu-psw-table.tex @@ -0,0 +1,31 @@ +\begin{longtable}{@{}L{27mm}L{18mm}L{114mm}@{}} +\toprule +\textbf{Selector} & \textbf{Bit} & \textbf{Смысл для JSET/JCLR и PSW}\\ +\midrule +\endfirsthead +\toprule +\textbf{Selector} & \textbf{Bit} & \textbf{Смысл для JSET/JCLR и PSW}\\ +\midrule +\endhead +AF & 0 & CPU arithmetic auxiliary/carry state; writable by \code{SETF/CLRF}\\ +CF & 1 & CPU carry/borrow flag; writable by \code{SETF/CLRF}\\ +OF & 2 & CPU integer overflow flag; writable by \code{SETF/CLRF}\\ +SF & 3 & CPU sign flag; writable by \code{SETF/CLRF}\\ +PF & 4 & CPU parity/auxiliary result classification flag; writable by \code{SETF/CLRF}\\ +ZF & 5 & CPU zero flag; writable by \code{SETF/CLRF}\\ +RF & 6 & CPU-visible LibMPU arithmetic status flag; writable by \code{SETF/CLRF}\\ +VF & 7 & CPU-visible LibMPU value/validity status flag; writable by \code{SETF/CLRF}\\ +DOMF & 8 & MPU math domain error state; writable by \code{SETF/CLRF}\\ +SNGF & 9 & MPU singularity error state; writable by \code{SETF/CLRF}\\ +OVFF & 10 & MPU floating/math overflow state; writable by \code{SETF/CLRF}\\ +UDFF & 11 & MPU floating/math underflow state; writable by \code{SETF/CLRF}\\ +TLSF & 12 & MPU total-loss-of-significance state; writable by \code{SETF/CLRF}\\ +PLSF & 13 & MPU partial-loss-of-significance state; writable by \code{SETF/CLRF}\\ +INDF & 14 & MPU invalid/indeterminate state; writable by \code{SETF/CLRF}\\ +INXF & 15 & MPU inexact state; writable by \code{SETF/CLRF}\\ +HF & 16 & HALT-related execution state; readable by \code{JSET/JCLR}, not writable by \code{SETF/CLRF}\\ +TF & 17 & trace/trap control state; readable by \code{JSET/JCLR}, not writable by \code{SETF/CLRF}\\ +IF & 18 & interrupt-enable control state; readable by \code{JSET/JCLR}, not writable by \code{SETF/CLRF}\\ +\bottomrule +\end{longtable} +\noindent Поле MODE является многобитным и намеренно не имеет selector для \code{JSET/JCLR}. Архитектурно PSW[7:0] образует CPU flag byte, PSW[15:8] --- MPU flag byte. \code{SETF/CLRF} разрешают запись только selectors 0..15; \code{CLRCPUF} очищает PSW[7:0], \code{CLRMPUF} --- PSW[15:8]. HF/TF/IF/MODE этими инструкциями не изменяются. Точные правила изменения PSW конкретными арифметическими и conversion instructions описаны в основной части руководства. diff --git a/doc/src/libmcpu-rconst-table.tex b/doc/src/libmcpu-rconst-table.tex new file mode 100644 index 0000000..3448a73 --- /dev/null +++ b/doc/src/libmcpu-rconst-table.tex @@ -0,0 +1,72 @@ +\begin{longtable}{@{}L{38mm}L{25mm}L{90mm}@{}} +\toprule +\textbf{Константа} & \textbf{CONST8} & \textbf{Смысл} \\ +\midrule +\endfirsthead +\toprule +\textbf{Константа} & \textbf{CONST8} & \textbf{Смысл} \\ +\midrule +\endhead +ZERO & \code{0x00} & Точный ноль. \\ +HALF & \code{0x01} & $1/2$. \\ +ONE & \code{0x02} & $1$. \\ +TWO & \code{0x03} & $2$. \\ +TEN & \code{0x04} & $10$. \\ +MTEN & \code{0x05} & $-10$. \\ +32 & \code{0x06} & $32$. \\ +PI & \code{0x10} & Число $\pi$. \\ +E & \code{0x11} & Основание натурального логарифма $e$. \\ +1\_LN2 & \code{0x12} & $1/\ln 2$. \\ +LN2 & \code{0x13} & $\ln 2$. \\ +1\_LN10 & \code{0x14} & $1/\ln 10$. \\ +LN10 & \code{0x15} & $\ln 10$. \\ +1\_LG2 & \code{0x16} & $1/\log_{10}2$. \\ +LG2 & \code{0x17} & $\log_{10}2$. \\ +PI\_2 & \code{0x20} & $\pi/2$. \\ +PI\_3 & \code{0x21} & $\pi/3$. \\ +PI\_4 & \code{0x22} & $\pi/4$. \\ +PI\_5 & \code{0x23} & $\pi/5$. \\ +PI\_6 & \code{0x24} & $\pi/6$. \\ +1\_PI & \code{0x25} & $1/\pi$. \\ +2\_PI & \code{0x26} & $2/\pi$. \\ +3\_PI & \code{0x27} & $3/\pi$. \\ +4\_PI & \code{0x28} & $4/\pi$. \\ +5\_PI & \code{0x29} & $5/\pi$. \\ +2PI & \code{0x2a} & $2\pi$. \\ +3PI & \code{0x2b} & $3\pi$. \\ +1\_2PI & \code{0x2c} & $1/(2\pi)$. \\ +1\_3PI & \code{0x2d} & $1/(3\pi)$. \\ +1\_4PI & \code{0x2e} & $1/(4\pi)$. \\ +1\_5PI & \code{0x2f} & $1/(5\pi)$. \\ +1\_6PI & \code{0x30} & $1/(6\pi)$. \\ +3PI\_4 & \code{0x31} & $3\pi/4$. \\ +SQRTPI & \code{0x40} & $\sqrt{\pi}$. \\ +1\_SQRTPI & \code{0x41} & $1/\sqrt{\pi}$. \\ +2\_SQRTPI & \code{0x42} & $2/\sqrt{\pi}$. \\ +SQRT2 & \code{0x43} & $\sqrt{2}$. \\ +1\_SQRT2 & \code{0x44} & $1/\sqrt{2}$. \\ +SQRT3 & \code{0x45} & $\sqrt{3}$. \\ +1\_SQRT3 & \code{0x46} & $1/\sqrt{3}$. \\ +DEGREE & \code{0x47} & Один градус в радианах, $\pi/180$. \\ +1\_DEGREE & \code{0x48} & Число градусов в одном радиане, $180/\pi$. \\ +GOLDENRATIO & \code{0x49} & Золотое сечение $(1+\sqrt{5})/2$. \\ +EULERGAMMA & \code{0x4a} & Постоянная Эйлера--Маскерони $\gamma$. \\ +CATALAN & \code{0x4b} & Постоянная Каталана $G$. \\ +EPSILON & \code{0x50} & LibMPU Real epsilon для выбранной физической ширины результата. \\ +IND & \code{0x51} & Специальное indeterminate-значение LibMPU. \\ +PNAN & \code{0x52} & Положительное NaN-представление LibMPU. \\ +NNAN & \code{0x53} & Отрицательное NaN-представление LibMPU. \\ +PNANMAX & \code{0x54} & Положительное NaN максимального класса LibMPU. \\ +NNANMAX & \code{0x55} & Отрицательное NaN максимального класса LibMPU. \\ +PNANMIN & \code{0x56} & Положительное NaN минимального класса LibMPU. \\ +NNANMIN & \code{0x57} & Отрицательное NaN минимального класса LibMPU. \\ +PINF & \code{0x58} & Положительная бесконечность. \\ +NINF & \code{0x59} & Отрицательная бесконечность. \\ +PREALMIN & \code{0x5a} & Минимальное положительное конечное Real-значение выбранной ширины. \\ +NREALMIN & \code{0x5b} & Минимальное по модулю отрицательное конечное Real-значение выбранной ширины. \\ +PREALMAX & \code{0x5c} & Максимальное положительное конечное Real-значение выбранной ширины. \\ +NREALMAX & \code{0x5d} & Максимальное по модулю отрицательное конечное Real-значение выбранной ширины. \\ +PZERO & \code{0x5e} & Положительный signed zero LibMPU. \\ +NZERO & \code{0x5f} & Отрицательный signed zero LibMPU. \\ +\bottomrule +\end{longtable} diff --git a/include/libmcpu.h b/include/libmcpu.h new file mode 100644 index 0000000..178f872 --- /dev/null +++ b/include/libmcpu.h @@ -0,0 +1,729 @@ + +/*************************************************************** + LIBMCPU.H + + This file contains public MCPU operation functions & data + declarations. + + PART OF : MCPU - library . + + Copyright (C) 2026 by Andrew V.Kosteltsev. + All Rights Reserved. + ***************************************************************/ + +#ifndef __LIB_MCPU_H +#define __LIB_MCPU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libmpu.h> + +#define CPU_REGISTERS_MEM_SIZE_IN_UNITS 524288 +#define CPU_REGISTER_ADDRESS_MAX 0x0007ffffU +#define CPU_REGISTER_MIN_BITS 8U +#define CPU_REGISTER_MAX_BITS 65536U + +/* LibCPU CALL ABI return-value area at the top of the register file. */ +#define CPU_RETURN_AREA_SIZE 0x00002000U +#define CPU_RETURN_AREA_BEGIN 0x0007e000U +#define CPU_RETURN_AREA_END 0x00080000U + +/* MCPU SYSCALL register ABI. */ +#define CPU_SYSCALL_NARGS_ADDR 0x0007fff8U +#define CPU_SYSCALL_SYSNO_ADDR 0x0007fffcU +#define CPU_SYSCALL_RET_ADDR 0x0007fff8U +#define CPU_SYSCALL_ARG0_ADDR 0x0007fff0U +#define CPU_SYSCALL_ARG1_ADDR 0x0007ffe8U +#define CPU_SYSCALL_ARG2_ADDR 0x0007ffe0U +#define CPU_SYSCALL_ARG3_ADDR 0x0007ffd8U +#define CPU_SYSCALL_ARG4_ADDR 0x0007ffd0U +#define CPU_SYSCALL_ARG5_ADDR 0x0007ffc8U +#define CPU_SYSCALL_MAX_ARGS 6U + + +/* Machine encoding sizes. */ +#define CPU_OPCODE_SIZE 2U +#define CPU_REG24_SIZE 3U +#define CPU_IMM64_SIZE 8U +#define CPU_ADDR64_SIZE 8U +#define CPU_CODE64_SIZE 8U +#define CPU_OFF64_SIZE 8U +#define CPU_CONST8_SIZE 1U +#define CPU_PSWBIT8_SIZE 1U +#define CPU_PTR64_SIZE 8U + +/* + * Byte-order domains. + * + * PROGRAM multi-byte machine fields are always canonical MSB-first. + * Architectural data objects (register file, DATA, STACK and future data + * segments) use the target data representation selected by LibMPU. + * CPU_DATA_* aliases make this ABI dependency explicit to LibCPU tools. + */ +#define CPU_CODE_BYTE_ORDER_MSB_FIRST 1 +#define CPU_DATA_BYTE_ORDER MPU_BYTE_ORDER +#define CPU_DATA_BYTE_ORDER_BIG_ENDIAN MPU_BYTE_ORDER_BIG_ENDIAN +#define CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN MPU_BYTE_ORDER_LITTLE_ENDIAN +#define CPU_DATA_WORD_ORDER MPU_WORD_ORDER +#define CPU_DATA_WORD_ORDER_BIG_ENDIAN MPU_WORD_ORDER_BIG_ENDIAN +#define CPU_DATA_WORD_ORDER_LITTLE_ENDIAN MPU_WORD_ORDER_LITTLE_ENDIAN + +extern __mpu_uint16_t __cpu_data_get_u16( const __mpu_UNIT_t *p ); +extern __mpu_uint32_t __cpu_data_get_u32( const __mpu_UNIT_t *p ); +extern __mpu_uint64_t __cpu_data_get_u64( const __mpu_UNIT_t *p ); +extern void __cpu_data_put_u16( __mpu_UNIT_t *p, __mpu_uint16_t v ); +extern void __cpu_data_put_u32( __mpu_UNIT_t *p, __mpu_uint32_t v ); +extern void __cpu_data_put_u64( __mpu_UNIT_t *p, __mpu_uint64_t v ); + +extern __mpu_uint16_t __cpu_code_get_u16( const __mpu_UNIT_t *p ); +extern __mpu_uint32_t __cpu_code_get_u32( const __mpu_UNIT_t *p ); +extern __mpu_uint64_t __cpu_code_get_u64( const __mpu_UNIT_t *p ); +extern void __cpu_code_put_u16( __mpu_UNIT_t *p, __mpu_uint16_t v ); +extern void __cpu_code_put_u32( __mpu_UNIT_t *p, __mpu_uint32_t v ); +extern void __cpu_code_put_u64( __mpu_UNIT_t *p, __mpu_uint64_t v ); + +/* Fixed CALL ABI bases. Individual argument offsets are computed inside + the aligned argument block and are therefore not generally fixed. */ +#define CPU_CALL_RETURN_PC_SIZE CPU_CODE64_SIZE +#define CPU_ABI_CALLEE_ARGUMENT_BLOCK_OFFSET 8U +#define CPU_ABI_FP_ARGUMENT_BLOCK_OFFSET 16U +#define CPU_ABI_HIDDEN_RESULT_BLOCK_OFFSET 0U + +/* LibCPU C-like ABI/data-layout conventions used by assembler/linker code. */ +#define CPU_ABI_MAX_ALIGN 16U +#define CPU_ABI_STACK_ALIGN 16U +#define CPU_ABI_STACK_RED_ZONE_SIZE 0U +#define CPU_ABI_ARGUMENT_BLOCK_ALIGN 16U +#define CPU_DATA_NULL_GUARD_SIZE 16U +#define CPU_HEAP_GUARD_SIZE 16U +#define CPU_HEAP_BEGIN CPU_HEAP_GUARD_SIZE +#define CPU_NULL_PTR __mpu_UINT64_C( 0x0000000000000000 ) + + +/*************************************************************** + PTR64 - segmented virtual pointer. + + bits 63..60 : 4-bit segment selector + bits 59..0 : byte offset inside the selected segment + + A PTR64 is an architectural value, never a host pointer. The + 60-bit offset gives each segment a 2^60-byte (1 EiB) virtual + address span. Segment identifiers 0x3..0x7 are assigned to model/runtime data + address spaces. Selectors 0x8..0xf remain reserved. + ***************************************************************/ +#define CPU_PTR_SEG_SHIFT 60U +#define CPU_PTR_SEG_MASK __mpu_UINT64_C( 0xf000000000000000 ) +#define CPU_PTR_OFFSET_MASK __mpu_UINT64_C( 0x0fffffffffffffff ) + +typedef enum __cpu_ptr_segment +{ + CPU_PTR_SEG_DATA = 0x0, + CPU_PTR_SEG_STACK = 0x1, + CPU_PTR_SEG_PROGRAM = 0x2, + CPU_PTR_SEG_CONTROL = 0x3, + CPU_PTR_SEG_INPUT = 0x4, + CPU_PTR_SEG_OUTPUT = 0x5, + CPU_PTR_SEG_TRACE = 0x6, + CPU_PTR_SEG_HEAP = 0x7 +} cpu_ptr_segment; + +#define CPU_PTR_SEG_FIRST_RESERVED 0x8U +#define CPU_PTR_SEG_MAX 0x0fU + +/* CONTROL and INPUT are guest read-only. OUTPUT, TRACE and HEAP are guest + read/write. PROGRAM is guest read-only. DATA and STACK remain guest + read/write. Reserved PTR64 selectors remain structurally valid pointer + values; non-dereferencing pointer arithmetic may preserve them, but any + operation requiring address-space meaning faults until a selector is + architecturally assigned. */ + +extern int __cpu_ptr_make ( __mpu_uint64_t *ptr, + unsigned int segment, + __mpu_uint64_t offset ); +extern unsigned int __cpu_ptr_segment( __mpu_uint64_t ptr ); +extern __mpu_uint64_t __cpu_ptr_offset ( __mpu_uint64_t ptr ); + +/*************************************************************** + PROGRAM STATUS WORD. + ***************************************************************/ +#define PSW_AF_MASK __mpu_UINT32_C( 0x00000001 ) +#define PSW_CF_MASK __mpu_UINT32_C( 0x00000002 ) +#define PSW_OF_MASK __mpu_UINT32_C( 0x00000004 ) +#define PSW_SF_MASK __mpu_UINT32_C( 0x00000008 ) +#define PSW_PF_MASK __mpu_UINT32_C( 0x00000010 ) +#define PSW_ZF_MASK __mpu_UINT32_C( 0x00000020 ) +#define PSW_RF_MASK __mpu_UINT32_C( 0x00000040 ) +#define PSW_VF_MASK __mpu_UINT32_C( 0x00000080 ) +#define PSW_DOMF_MASK __mpu_UINT32_C( 0x00000100 ) +#define PSW_SNGF_MASK __mpu_UINT32_C( 0x00000200 ) +#define PSW_OVFF_MASK __mpu_UINT32_C( 0x00000400 ) +#define PSW_UDFF_MASK __mpu_UINT32_C( 0x00000800 ) +#define PSW_TLSF_MASK __mpu_UINT32_C( 0x00001000 ) +#define PSW_PLSF_MASK __mpu_UINT32_C( 0x00002000 ) +#define PSW_INDF_MASK __mpu_UINT32_C( 0x00004000 ) +#define PSW_INXF_MASK __mpu_UINT32_C( 0x00008000 ) +#define PSW_HF_MASK __mpu_UINT32_C( 0x00010000 ) +#define PSW_TF_MASK __mpu_UINT32_C( 0x00020000 ) +#define PSW_IF_MASK __mpu_UINT32_C( 0x00040000 ) +#define PSW_MODE_MASK __mpu_UINT32_C( 0x00180000 ) +/* Architectural arithmetic/status partition. The lower byte is the CPU + flag domain and the upper byte is the MPU mathematical flag domain. The + implementation-level PSW_MPU_MASK name is retained for the full LibMPU-backed + PSW[15:0] facade used by existing arithmetic primitives. */ +#define PSW_CPUF_MASK __mpu_UINT32_C( 0x000000ff ) +#define PSW_MPUF_MASK __mpu_UINT32_C( 0x0000ff00 ) +#define PSW_MPU_MASK __mpu_UINT32_C( 0x0000ffff ) +#define PSW_CPU_MASK __mpu_UINT32_C( 0x001f0000 ) +#define PSW_USED_MASK __mpu_UINT32_C( 0x001fffff ) +#define PSW_HF_SHIFT 16 +#define PSW_TF_SHIFT 17 +#define PSW_IF_SHIFT 18 +#define PSW_MODE_SHIFT 19 + +/* + * Normal-function PSW calling convention. + * + * CALL/CALLP/RET themselves are PSW-neutral: they do not save, restore or + * modify PSW. The masks below describe the ABI contract across a normal + * function call. + * + * PSW[15:0] is the arithmetic/status domain and is caller-clobbered. + * PSW[7:0] is the CPU flag byte; PSW[15:8] is the MPU mathematical flag byte. + * TF, IF and MODE are persistent control state; a callee that changes any of + * them must restore the incoming value before an ordinary RET. + * HF is execution state, not a caller/callee-saved function value: HALT ends + * normal control flow, while CALL/CALLP/RET themselves leave HF unchanged. + */ +#define CPU_PSW_ABI_VOLATILE_STATUS_MASK PSW_MPU_MASK +#define CPU_PSW_ABI_PERSISTENT_CONTROL_MASK \ + (PSW_TF_MASK | PSW_IF_MASK | PSW_MODE_MASK) +#define CPU_PSW_ABI_EXECUTION_STATE_MASK PSW_HF_MASK + +extern void __cpu_init( void ); +extern void __cpu_free_context( void ); + +extern __mpu_uint64_t *__ptr_cpu_pc( void ); +extern __mpu_uint64_t *__ptr_cpu_sp( void ); +extern __mpu_uint64_t *__ptr_cpu_fp( void ); +extern __mpu_UNIT_t *__ptr_cpu_registers( void ); +#define __cpu_pc (*__ptr_cpu_pc()) +#define __cpu_sp (*__ptr_cpu_sp()) +#define __cpu_fp (*__ptr_cpu_fp()) +#define __cpu_registers (__ptr_cpu_registers()) + +/*************************************************************** + PHYSICAL REGISTER VIEWS. + + REG24 machine encoding: + + bits 23..20 : WIDTH code, 0..13 => 8..65536 bits + bits 19..0 : byte address; bit 19 is MBZ for the current + 512 KiB register file. + + Source assembler uses r<bits>@addr for integer/real views and + c<bits>@addr for complex views. In both forms <bits> is the + PHYSICAL register width. A c64 view therefore contains two + real32 components and has exactly the same REG24 encoding as + r64 at the same address. + ***************************************************************/ +typedef struct __cpu_register cpu_register; +struct __cpu_register +{ + unsigned int bits; + __mpu_uint32_t address; +}; + +extern __mpu_size_t __cpu_reg_size ( unsigned int bits ); +extern int __cpu_reg_valid( unsigned int bits, __mpu_uint32_t address ); +extern __mpu_UNIT_t *__cpu_reg_ptr ( unsigned int bits, __mpu_uint32_t address ); + +/* Canonical ABI return register: addr = 0x80000 - physical_size. */ +extern int __cpu_return_reg ( cpu_register *reg, unsigned int bits ); + +extern int __cpu_encode_reg24( __mpu_UNIT_t out[CPU_REG24_SIZE], + const cpu_register *reg ); +extern int __cpu_decode_reg24( cpu_register *reg, + const __mpu_UNIT_t in[CPU_REG24_SIZE] ); + +/*************************************************************** + PSW access. + ***************************************************************/ +extern __mpu_uint32_t __cpu_get_psw ( void ); +extern void __cpu_set_psw ( __mpu_uint32_t psw ); +extern void __cpu_clear_psw ( void ); +extern int __cpu_gth ( void ); +extern int __cpu_gtt ( void ); +extern int __cpu_gti ( void ); +extern void __cpu_sth ( void ); +extern void __cpu_stt ( void ); +extern void __cpu_sti ( void ); +extern void __cpu_clh ( void ); +extern void __cpu_clt ( void ); +extern void __cpu_cli ( void ); +extern void __cpu_cmh ( void ); +extern void __cpu_cmt ( void ); +extern void __cpu_cmi ( void ); +extern int __cpu_gtmode ( void ); +extern int __cpu_stmode ( unsigned int mode ); + +/*************************************************************** + ARCHITECTURAL MEMORY SPACES. + + PROGRAM, DATA, STACK, CONTROL, INPUT, OUTPUT, TRACE and HEAP are + independent byte-addressable guest spaces backed by anonymous + mmap() mappings. Architectural bounds always use segment size, + never host page-rounded capacity. No guest segment is file-backed. + + PROGRAM is host-protected read-only after loading. CONTROL and + INPUT remain host-writable so a runtime/supervisor can update them, + but guest STOREP/BMOV destinations to those spaces fault. OUTPUT, + TRACE and HEAP are guest read/write. + + STACK grows toward lower addresses; an empty allocated stack has + SP == FP == stack_size. + + HEAP is zero-filled runtime storage and has no assembler payload. + Offsets [0,CPU_HEAP_BEGIN) are reserved. The current API records + initial architectural size and a maximum limit. malloc/free are + library concerns; dynamic heap growth is intentionally reserved for + a future runtime/system-call ABI. + ***************************************************************/ +extern int __cpu_program_alloc ( __mpu_uint64_t size ); +extern void __cpu_program_free ( void ); +extern int __cpu_program_protect ( void ); +extern __mpu_UNIT_t *__cpu_program_ptr ( void ); +extern __mpu_uint64_t __cpu_program_size ( void ); +extern int __cpu_program_set ( const __mpu_UNIT_t *data, + __mpu_uint64_t size ); +extern int __cpu_data_alloc ( __mpu_uint64_t size ); +extern void __cpu_data_free ( void ); +extern __mpu_UNIT_t *__cpu_data_ptr ( void ); +extern __mpu_uint64_t __cpu_data_size ( void ); +extern int __cpu_data_set ( const __mpu_UNIT_t *data, + __mpu_uint64_t size ); +extern int __cpu_stack_alloc ( __mpu_uint64_t size ); +extern void __cpu_stack_free ( void ); +extern __mpu_UNIT_t *__cpu_stack_ptr ( void ); +extern __mpu_uint64_t __cpu_stack_size ( void ); +extern int __cpu_control_alloc ( __mpu_uint64_t size ); +extern void __cpu_control_free ( void ); +extern __mpu_UNIT_t *__cpu_control_ptr ( void ); +extern __mpu_uint64_t __cpu_control_size ( void ); +extern int __cpu_control_set ( const __mpu_UNIT_t *data, __mpu_uint64_t size ); +extern int __cpu_input_alloc ( __mpu_uint64_t size ); +extern void __cpu_input_free ( void ); +extern __mpu_UNIT_t *__cpu_input_ptr ( void ); +extern __mpu_uint64_t __cpu_input_size ( void ); +extern int __cpu_input_set ( const __mpu_UNIT_t *data, __mpu_uint64_t size ); +extern int __cpu_output_alloc ( __mpu_uint64_t size ); +extern void __cpu_output_free ( void ); +extern __mpu_UNIT_t *__cpu_output_ptr ( void ); +extern __mpu_uint64_t __cpu_output_size ( void ); +extern int __cpu_output_set ( const __mpu_UNIT_t *data, __mpu_uint64_t size ); +extern int __cpu_trace_alloc ( __mpu_uint64_t size ); +extern void __cpu_trace_free ( void ); +extern __mpu_UNIT_t *__cpu_trace_ptr ( void ); +extern __mpu_uint64_t __cpu_trace_size ( void ); +extern int __cpu_trace_set ( const __mpu_UNIT_t *data, __mpu_uint64_t size ); +extern int __cpu_heap_alloc ( __mpu_uint64_t initial_size, __mpu_uint64_t maximum_size ); +extern void __cpu_heap_free ( void ); +extern __mpu_UNIT_t *__cpu_heap_ptr ( void ); +extern __mpu_uint64_t __cpu_heap_size ( void ); +extern __mpu_uint64_t __cpu_heap_limit ( void ); + +/*************************************************************** + MACHINE OPCODES. + + The opcode completely determines the number and kind of all + following operands. Multi-byte machine fields use canonical + MSB-first byte order. + ***************************************************************/ +typedef enum __cpu_opcode +{ + CPU_OP_NOP = 0x0000, + CPU_OP_HALT = 0x0001, + CPU_OP_END = 0x0002, /* end current externally managed execution phase */ + CPU_OP_EXIT = 0x0003, /* r32 signed program/model exit status */ + CPU_OP_SYSCALL = 0x0004, /* system call through fixed register ABI */ + + CPU_OP_SETF = 0x0010, /* set one writable PSW[15:0] flag */ + CPU_OP_CLRF = 0x0011, /* clear one writable PSW[15:0] flag */ + CPU_OP_CLRCPUF = 0x0012, /* clear CPU flag byte PSW[7:0] */ + CPU_OP_CLRMPUF = 0x0013, /* clear MPU flag byte PSW[15:8] */ + + CPU_OP_MOV = 0x0100, + CPU_OP_CLR = 0x0101, + CPU_OP_SWAP = 0x0102, + CPU_OP_BSWAP = 0x0103, /* raw byte reversal: Rdst, Rsrc */ + CPU_OP_LDI = 0x0110, /* R, signed integer64 immediate */ + CPU_OP_LDR = 0x0111, /* R, IEEE binary64 immediate */ + CPU_OP_LDC = 0x0112, /* R, 2 x IEEE binary32 */ + CPU_OP_LOAD = 0x0120, /* R, DATA ADDR64 */ + CPU_OP_STORE = 0x0121, /* DATA ADDR64, R */ + CPU_OP_LEA = 0x0130, /* r64, PTR64 immediate */ + CPU_OP_LEASP = 0x0131, /* r64, signed OFF64 -> PTR64 */ + CPU_OP_LOADP = 0x0132, /* R/C, r64(PTR64) */ + CPU_OP_STOREP = 0x0133, /* r64(PTR64), R/C */ + CPU_OP_PADD = 0x0140, /* r64 = PTR64 + signed r64 */ + CPU_OP_PADDI = 0x0141, /* r64 = PTR64 + signed OFF64 */ + CPU_OP_PSUB = 0x0142, /* r64 = PTR64 - signed r64 */ + CPU_OP_PSUBI = 0x0143, /* r64 = PTR64 - signed OFF64 */ + CPU_OP_PDIFF = 0x0144, /* r64 = PTR64 - PTR64 bytes */ + CPU_OP_BMOV = 0x0150, /* PTR64 dst, PTR64 src, r64 byte count */ + CPU_OP_BMOVI = 0x0151, /* PTR64 dst, PTR64 src, IMM64 byte count */ + + CPU_OP_IADD = 0x1000, + CPU_OP_IADC = 0x1001, + CPU_OP_ISUB = 0x1002, + CPU_OP_ISBB = 0x1003, + CPU_OP_IAND = 0x1004, + CPU_OP_IOR = 0x1005, + CPU_OP_IXOR = 0x1006, + CPU_OP_INOT = 0x1010, + CPU_OP_INEG = 0x1011, + CPU_OP_ICMP = 0x1020, + CPU_OP_ITEST = 0x1021, + + CPU_OP_ISHL = 0x1030, /* logical shift left by one bit */ + CPU_OP_ISHR = 0x1031, /* logical shift right by one bit */ + CPU_OP_ISAL = 0x1032, /* arithmetic shift left by one bit */ + CPU_OP_ISAR = 0x1033, /* arithmetic shift right by one bit */ + CPU_OP_IROL = 0x1034, /* rotate left by one bit */ + CPU_OP_IROR = 0x1035, /* rotate right by one bit */ + CPU_OP_IRCL = 0x1036, /* rotate left through CF by one bit */ + CPU_OP_IRCR = 0x1037, /* rotate right through CF by one bit */ + + CPU_OP_ISHLN = 0x1040, /* Rdst,Rsrc,r64 count */ + CPU_OP_ISHRN = 0x1041, + CPU_OP_ISALN = 0x1042, + CPU_OP_ISARN = 0x1043, + CPU_OP_IROLN = 0x1044, + CPU_OP_IRORN = 0x1045, + CPU_OP_IRCLN = 0x1046, + CPU_OP_IRCRN = 0x1047, + + CPU_OP_IINC = 0x1050, + CPU_OP_IDEC = 0x1051, + CPU_OP_IXCHG = 0x1052, + CPU_OP_ICPY = 0x1053, /* width-changing unsigned copy */ + CPU_OP_ICVT = 0x1054, /* width-changing signed conversion */ + + CPU_OP_IMUL = 0x1060, /* unsigned full product */ + CPU_OP_ISMUL = 0x1061, /* signed full product */ + CPU_OP_IDIV = 0x1062, /* quot,rem,num,den (unsigned) */ + CPU_OP_ISDIV = 0x1063, /* quot,rem,num,den (signed) */ + CPU_OP_IBITSLEN = 0x1064, /* r64 result, integer source */ + + CPU_OP_RADD = 0x2000, + CPU_OP_RSUB = 0x2001, + CPU_OP_RMUL = 0x2002, + CPU_OP_RDIV = 0x2003, + CPU_OP_RSQRT = 0x2010, + CPU_OP_RSIN = 0x2020, + CPU_OP_RCOS = 0x2021, + CPU_OP_RTAN = 0x2022, + CPU_OP_RASIN = 0x2023, + CPU_OP_RACOS = 0x2024, + CPU_OP_RATAN = 0x2025, + CPU_OP_RLOG = 0x2030, + CPU_OP_RLOG10 = 0x2031, + CPU_OP_RLOG2 = 0x2032, + CPU_OP_REXP = 0x2033, + CPU_OP_RSINH = 0x2040, + CPU_OP_RCOSH = 0x2041, + CPU_OP_RTANH = 0x2042, + CPU_OP_RPOW = 0x2050, + CPU_OP_RHYPOT = 0x2051, + CPU_OP_RATAN2 = 0x2052, + + CPU_OP_RNEG = 0x2060, + CPU_OP_RABS = 0x2061, + CPU_OP_RCMP = 0x2062, /* r64 result {-2,-1,0,+1}, Ra, Rb */ + CPU_OP_RCPY = 0x2063, + CPU_OP_RCVT = 0x2064, + + CPU_OP_RFLOOR = 0x2070, + CPU_OP_RCEIL = 0x2071, + CPU_OP_RROUND = 0x2072, + CPU_OP_RFREXP = 0x2073, /* R significand, I exponent, R source */ + CPU_OP_RLDEXP = 0x2074, /* R dst, R src, I exponent */ + CPU_OP_RLOGB = 0x2075, /* I dst, R src */ + CPU_OP_RMODF = 0x2076, /* R frac, R integer, R source */ + CPU_OP_RREMAIN = 0x2077, + + CPU_OP_RLOG1P = 0x2080, + CPU_OP_REXPM1 = 0x2081, + CPU_OP_RASINH = 0x2082, + CPU_OP_RACOSH = 0x2083, + CPU_OP_RATANH = 0x2084, + + CPU_OP_ITOR = 0x2090, /* signed integer -> real */ + CPU_OP_UITOR = 0x2091, /* unsigned integer -> real */ + CPU_OP_RTOI = 0x2092, /* I dst, R frac, R src */ + CPU_OP_RTOUI = 0x2093, /* UI dst, R frac, R src */ + + CPU_OP_RISNEG = 0x20a0, /* r64 predicate, R src */ + CPU_OP_RSIGN = 0x20a1, /* r64 signed {-3..+1}, R src */ + CPU_OP_RISIND = 0x20a2, + CPU_OP_RISNAN = 0x20a3, + CPU_OP_RISNANMAX = 0x20a4, + CPU_OP_RISNANMIN = 0x20a5, + CPU_OP_RISINF = 0x20a6, + CPU_OP_RISSIGNULL = 0x20a7, + + CPU_OP_RCONST = 0x2100, /* R, CONST8 */ + + CPU_OP_CADD = 0x3000, + CPU_OP_CSUB = 0x3001, + CPU_OP_CMUL = 0x3002, + CPU_OP_CDIV = 0x3003, + CPU_OP_CEXP = 0x3020, + CPU_OP_CSIN = 0x3021, + CPU_OP_CCOS = 0x3022, + CPU_OP_CSINH = 0x3023, + CPU_OP_CCOSH = 0x3024, + CPU_OP_CLOG = 0x3025, + CPU_OP_CSQRT = 0x3026, + CPU_OP_CPOW = 0x3050, + + CPU_OP_CREAL = 0x3060, /* R component dst, C src */ + CPU_OP_CIMAG = 0x3061, /* R component dst, C src */ + CPU_OP_CRTOC = 0x3062, /* C dst, R src */ + CPU_OP_CGEN = 0x3063, /* C dst, R real, R imaginary */ + CPU_OP_CPOLAR = 0x3064, /* C dst, R rho, R theta */ + CPU_OP_CCONJ = 0x3065, + CPU_OP_CABS = 0x3066, /* R dst, C src */ + CPU_OP_CNORM = 0x3067, /* R dst, C src */ + CPU_OP_CARG = 0x3068, /* R dst, C src */ + + CPU_OP_CISEQ = 0x3070, /* r64 predicate, C a, C b */ + CPU_OP_CISNE = 0x3071, + CPU_OP_CCPY = 0x3072, + CPU_OP_CCVT = 0x3073, + + CPU_OP_JMP = 0x4000, /* CODE64 */ + CPU_OP_JSET = 0x4001, /* PSWBIT8, CODE64 */ + CPU_OP_JCLR = 0x4002, /* PSWBIT8, CODE64 */ + CPU_OP_JMPP = 0x4003, /* r64(PROGRAM PTR64) */ + CPU_OP_CALL = 0x4010, /* CODE64; push next PC */ + CPU_OP_RET = 0x4011, /* pop PC from STACK */ + CPU_OP_CALLP = 0x4012, /* r64(PROGRAM PTR64); push next PC */ + + CPU_OP_PUSH = 0x5000, /* R/C */ + CPU_OP_POP = 0x5001, /* R/C */ + CPU_OP_LDSP = 0x5010, /* R/C, signed OFF64 */ + CPU_OP_STSP = 0x5011, /* signed OFF64, R/C */ + CPU_OP_ADJSP = 0x5020, /* signed OFF64 */ + + CPU_OP_PUSHFP = 0x5030, /* push current 64-bit FP on STACK */ + CPU_OP_SETFP = 0x5031, /* FP = SP */ + CPU_OP_RESTSP = 0x5032, /* SP = FP */ + CPU_OP_POPFP = 0x5033, /* pop 64-bit FP from STACK */ + + CPU_OP_LDFP = 0x5040, /* R/C, signed OFF64 relative to FP */ + CPU_OP_STFP = 0x5041, /* signed OFF64 relative to FP, R/C */ + CPU_OP_LEAFP = 0x5042, /* r64, signed OFF64 -> STACK PTR64 */ + + CPU_OP_SETSP = 0x5050 /* r64(STACK PTR64) -> SP */ +} cpu_opcode; + + + +/*************************************************************** + PSW bit selectors used by JSET/JCLR and by writable-flag + instructions where explicitly permitted. + + MODE is a multi-bit field and is intentionally not + addressable through JSET/JCLR. Branch selectors cover + individual bits 0..18. SETF/CLRF are restricted to bits + 0..15 by CPU_PSWBIT_WRITE_MAX. + ***************************************************************/ +typedef enum __cpu_psw_bit +{ + CPU_PSWBIT_AF = 0, + CPU_PSWBIT_CF = 1, + CPU_PSWBIT_OF = 2, + CPU_PSWBIT_SF = 3, + CPU_PSWBIT_PF = 4, + CPU_PSWBIT_ZF = 5, + CPU_PSWBIT_RF = 6, + CPU_PSWBIT_VF = 7, + CPU_PSWBIT_DOMF = 8, + CPU_PSWBIT_SNGF = 9, + CPU_PSWBIT_OVFF = 10, + CPU_PSWBIT_UDFF = 11, + CPU_PSWBIT_TLSF = 12, + CPU_PSWBIT_PLSF = 13, + CPU_PSWBIT_INDF = 14, + CPU_PSWBIT_INXF = 15, + CPU_PSWBIT_HF = 16, + CPU_PSWBIT_TF = 17, + CPU_PSWBIT_IF = 18 +} cpu_psw_bit; + +#define CPU_PSWBIT_MAX CPU_PSWBIT_IF +#define CPU_PSWBIT_WRITE_MAX CPU_PSWBIT_INXF + +/*************************************************************** + Predefined real mathematical constants for RCONST. + ***************************************************************/ +typedef enum __cpu_real_constant +{ + CPU_RCONST_ZERO = 0x00, + CPU_RCONST_HALF, + CPU_RCONST_ONE, + CPU_RCONST_TWO, + CPU_RCONST_TEN, + CPU_RCONST_MTEN, + CPU_RCONST_32, + + CPU_RCONST_PI = 0x10, + CPU_RCONST_E, + CPU_RCONST_1_LN2, + CPU_RCONST_LN2, + CPU_RCONST_1_LN10, + CPU_RCONST_LN10, + CPU_RCONST_1_LG2, + CPU_RCONST_LG2, + + CPU_RCONST_PI_2 = 0x20, + CPU_RCONST_PI_3, + CPU_RCONST_PI_4, + CPU_RCONST_PI_5, + CPU_RCONST_PI_6, + CPU_RCONST_1_PI, + CPU_RCONST_2_PI, + CPU_RCONST_3_PI, + CPU_RCONST_4_PI, + CPU_RCONST_5_PI, + CPU_RCONST_2PI, + CPU_RCONST_3PI, + CPU_RCONST_1_2PI, + CPU_RCONST_1_3PI, + CPU_RCONST_1_4PI, + CPU_RCONST_1_5PI, + CPU_RCONST_1_6PI, + CPU_RCONST_3PI_4, + + CPU_RCONST_SQRTPI = 0x40, + CPU_RCONST_1_SQRTPI, + CPU_RCONST_2_SQRTPI, + CPU_RCONST_SQRT2, + CPU_RCONST_1_SQRT2, + CPU_RCONST_SQRT3, + CPU_RCONST_1_SQRT3, + CPU_RCONST_DEGREE, + CPU_RCONST_1_DEGREE, + CPU_RCONST_GOLDENRATIO, + CPU_RCONST_EULERGAMMA, + CPU_RCONST_CATALAN, + + CPU_RCONST_EPSILON = 0x50, + CPU_RCONST_IND, + CPU_RCONST_PNAN, + CPU_RCONST_NNAN, + CPU_RCONST_PNANMAX, + CPU_RCONST_NNANMAX, + CPU_RCONST_PNANMIN, + CPU_RCONST_NNANMIN, + CPU_RCONST_PINF, + CPU_RCONST_NINF, + CPU_RCONST_PREALMIN, + CPU_RCONST_NREALMIN, + CPU_RCONST_PREALMAX, + CPU_RCONST_NREALMAX, + CPU_RCONST_PZERO, + CPU_RCONST_NZERO +} cpu_real_constant; + +/*************************************************************** + DECODED INSTRUCTION REPRESENTATION. + ***************************************************************/ +typedef enum __cpu_operand_type +{ + CPU_OPERAND_NONE = 0, + CPU_OPERAND_REGISTER, + CPU_OPERAND_IMM64, + CPU_OPERAND_ADDR64, + CPU_OPERAND_CODE64, + CPU_OPERAND_OFF64, + CPU_OPERAND_CONST8, + CPU_OPERAND_PSWBIT8, + CPU_OPERAND_PTR64 +} cpu_operand_type; + +#define CPU_OPERAND_IMMEDIATE CPU_OPERAND_IMM64 + +typedef struct __cpu_operand cpu_operand; +struct __cpu_operand +{ + cpu_operand_type type; + union + { + cpu_register reg; + __mpu_uint64_t imm64; + __mpu_uint64_t addr64; + __mpu_uint64_t code64; + __mpu_int64_t off64; + __mpu_uint8_t const8; + __mpu_uint8_t pswbit8; + __mpu_uint64_t ptr64; + } value; +}; + +#define CPU_INSTRUCTION_MAX_OPERANDS 4 + +typedef struct __cpu_instruction cpu_instruction; +struct __cpu_instruction +{ + cpu_opcode opcode; + cpu_operand operand[CPU_INSTRUCTION_MAX_OPERANDS]; + __mpu_uint64_t length; +}; + +#define CPU_EXEC_SUCCESS 0 +#define CPU_EXEC_INSTRUCTION_FAULT -1 +#define CPU_EXEC_OPCODE_FAULT -2 +#define CPU_EXEC_OPERAND_FAULT -3 +#define CPU_EXEC_REGISTER_FAULT -4 +#define CPU_EXEC_WIDTH_FAULT -5 +#define CPU_EXEC_MEMORY_FAULT -6 +#define CPU_EXEC_DECODE_FAULT -7 +#define CPU_EXEC_STACK_FAULT -8 +#define CPU_EXEC_POINTER_FAULT -9 +#define CPU_EXEC_HALT 1 +#define CPU_EXEC_BRANCH 2 +#define CPU_EXEC_END 3 +#define CPU_EXEC_EXIT 4 + +typedef enum __cpu_stop_reason +{ + CPU_STOP_NONE = 0, + CPU_STOP_END, + CPU_STOP_EXIT, + CPU_STOP_HALT, + CPU_STOP_TRAP, + CPU_STOP_FAULT, + CPU_STOP_KILLED +} cpu_stop_reason; + +extern cpu_stop_reason __cpu_get_stop_reason( void ); +extern __mpu_int32_t __cpu_get_exit_status( void ); +extern void __cpu_clear_stop_reason( void ); + +extern int __cpu_exec ( const cpu_instruction *instruction ); +extern int __cpu_decode_at( const __mpu_UNIT_t *program, + __mpu_uint64_t program_size, + __mpu_uint64_t pc, + cpu_instruction *instruction ); +extern int __cpu_decode ( cpu_instruction *instruction ); +extern int __cpu_step ( void ); +extern int __cpu_run ( void ); + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* __LIB_MCPU_H */ diff --git a/include/mcpu-elf.h b/include/mcpu-elf.h new file mode 100644 index 0000000..f8436a9 --- /dev/null +++ b/include/mcpu-elf.h @@ -0,0 +1,3120 @@ +/* + * mcpu-elf.h + * + * MELF (MCPU ELF-derived format) definitions for LibMCPU/MCPU. + * + * Implemented specification stages: + * + * 1. MELF identification and file-header ABI. + * 2. MELF ET_REL / ET_EXEC file model and lifecycle entries. + * 3. MELF section ABI and MCPU architectural address-space mapping. + * 4. MELF symbol ABI. + * 5. MELF RELA relocation ABI. + * 6. MCPU target-profile and lifecycle metadata. + * 7. MELF program-header ABI and MCPU segment mapping. + * 8. Static-linker, loader/startup and runtime lifecycle contract. + * + * Stages 1-8 define the original static ABI-v1 contract. LibMCPU 0.1.0 + * extends this header with MELF ABI-v2 startup dynamic linking (ET_DYN, + * .dynsym/.dynstr and retained startup RELA). LibMCPU 0.2.14 introduces + * development ABI-v3 dependency metadata (.dynamic, SONAME and NEEDED) + * while keeping ABI-v1 and ABI-v2 files supported. + * + * The current MCPU ABIs are MELF64. MELF32 names are reserved for a + * possible future 32-bit MCPU ABI and are accepted by neither ABI v1, + * ABI v2 nor ABI v3. + * + * IMPORTANT + * --------- + * The C structures below describe logical MELF fields. They are not an + * on-disk serialization mechanism. MELF readers and writers must + * encode/decode every multi-byte field explicitly according to EI_DATA. + * Do not read/write MELF headers with structure casts or by fwrite()/ + * fread() of the structures, and do not depend on host alignment, + * structure padding or host endian. + * + * PROGRAM section payload is a separate byte-order domain. LibMCPU + * machine fields (OPCODE16, REG24, IMM64, ADDR64, CODE64, OFF64, ...) + * remain canonical MSB-first regardless of MELF EI_DATA. + */ + +#ifndef MCPU_ELF_H +#define MCPU_ELF_H + +#include <libmpu.h> + + +/* + * -------------------------------------------------------------------------- + * MELF identification + * -------------------------------------------------------------------------- + */ + +/* e_ident[] indices. */ +#define MELF_EI_MAG0 0 +#define MELF_EI_MAG1 1 +#define MELF_EI_MAG2 2 +#define MELF_EI_MAG3 3 +#define MELF_EI_CLASS 4 +#define MELF_EI_DATA 5 +#define MELF_EI_VERSION 6 +#define MELF_EI_OSABI 7 +#define MELF_EI_ABIVERSION 8 +#define MELF_EI_PAD 9 +#define MELF_EI_NIDENT 16 + + +/* + * MELF magic. + * + * MELF intentionally does not use the ordinary ELF 0x7f,'E','L','F' + * signature. "_ELF" identifies the MCPU ELF-derived container and + * prevents accidental interpretation by ordinary host ELF loaders. + */ +#define MELFMAG0 ((__mpu_uint8_t)'_') +#define MELFMAG1 ((__mpu_uint8_t)'E') +#define MELFMAG2 ((__mpu_uint8_t)'L') +#define MELFMAG3 ((__mpu_uint8_t)'F') + + +/* + * File class. + * + * ABI versions 1 and 2 accept MELF_CLASS_64 only. + * MELF_CLASS_32 is reserved for a possible future MCPU32 ABI. + * + * MELF class is a TARGET property. It must never be selected from the + * build host's sizeof(void *), sizeof(long), ILP32/LP64/LLP64 model, etc. + */ +#define MELF_CLASS_NONE 0 +#define MELF_CLASS_32 1 +#define MELF_CLASS_64 2 + + +/* + * MELF metadata/data-object encoding. + * + * EI_DATA controls the byte order of MELF multi-byte metadata fields and + * the target architectural data-object representation. + * + * It does NOT change PROGRAM instruction serialization: PROGRAM machine + * fields remain canonical MSB-first. + * + * LibMPU word-order information that cannot be represented by this field + * belongs to future MCPU target-profile metadata. + */ +#define MELF_DATA_NONE 0 +#define MELF_DATA_LSB 1 +#define MELF_DATA_MSB 2 + + +/* MELF format version. */ +#define MELF_EV_NONE 0 +#define MELF_EV_CURRENT 1 + + +/* + * MCPU execution ABI. + * + * The OSABI value identifies the MCPU execution environment, usable both + * for ordinary programs and for managed program models. + */ +#define MELF_OSABI_MCPU 64 +#define MELF_ABIVERSION_MCPU_1 1 +#define MELF_ABIVERSION_MCPU_2 2 +#define MELF_ABIVERSION_MCPU_3 3 + +/* + * ABI 1 is the static object/executable contract. + * ABI 2 is the historical explicit-preload startup dynamic-link contract. + * ABI 3 adds .dynamic, SONAME and NEEDED metadata and is the frozen + * normal dynamic-link ABI used by current --dynamic/--shared outputs. + */ +#define MELF_ABIVERSION_STATIC MELF_ABIVERSION_MCPU_1 +#define MELF_ABIVERSION_DYNAMIC_EXPLICIT MELF_ABIVERSION_MCPU_2 +#define MELF_ABIVERSION_DYNAMIC_NEEDED MELF_ABIVERSION_MCPU_3 + +/* Historical source alias used by the ABI-v2 writer/loader paths. */ +#define MELF_ABIVERSION_DYNAMIC MELF_ABIVERSION_DYNAMIC_EXPLICIT + +/* Historical/current default used by ET_REL and static ET_EXEC writers. */ +#define MELF_ABIVERSION_CURRENT MELF_ABIVERSION_STATIC + + +/* + * Object file types. + * + * ABI version 1 supports ET_REL and fully-static ET_EXEC. + * ABI version 2 adds startup-dynamic ET_EXEC and explicit-preload ET_DYN. + * ABI version 3 adds dependency metadata to dynamic ET_EXEC/ET_DYN. + * ET_CORE remains reserved and unsupported. + */ +#define MELF_ET_NONE 0 +#define MELF_ET_REL 1 +#define MELF_ET_EXEC 2 +#define MELF_ET_DYN 3 +#define MELF_ET_CORE 4 + + +/* + * Machine architecture. + * + * MELF_EM_MCPU is a private MELF value, not an official ELF e_machine + * assignment. 0x4d43 is ASCII "MC". + * + * MCPU32/MCPU64 are not separate e_machine values. Machine architecture + * is MCPU; container/address class is selected by EI_CLASS, while a future + * C-like language programming model (ILP32/LP64/LLP64, etc.) belongs to + * target-profile ABI metadata. + */ +#define MELF_EM_NONE 0x0000 +#define MELF_EM_MCPU 0x4d43 + + +/* + * Processor-specific e_flags. + * + * No e_flags bits are assigned in ABI version 1. A conforming ABI-v1 + * writer emits zero and a conforming ABI-v1 loader requires zero. + */ +#define MELF_EF_MCPU_NONE 0x00000000U + + +/* + * -------------------------------------------------------------------------- + * MELF scalar types + * -------------------------------------------------------------------------- + */ + +/* MELF32 scalar types: reserved for a possible future MCPU32 ABI. */ +typedef __mpu_uint16_t MElf32_Half; +typedef __mpu_uint32_t MElf32_Word; +typedef __mpu_int32_t MElf32_Sword; +typedef __mpu_uint32_t MElf32_Addr; +typedef __mpu_uint32_t MElf32_Off; + + +/* MELF64 scalar types. */ +typedef __mpu_uint16_t MElf64_Half; +typedef __mpu_uint32_t MElf64_Word; +typedef __mpu_int32_t MElf64_Sword; +typedef __mpu_uint64_t MElf64_Xword; +typedef __mpu_int64_t MElf64_Sxword; +typedef __mpu_uint64_t MElf64_Addr; +typedef __mpu_uint64_t MElf64_Off; + + +/* + * -------------------------------------------------------------------------- + * MELF file header + * -------------------------------------------------------------------------- + */ + +/* + * Lifecycle ABI for MELF_ET_EXEC. + * + * _start is mandatory. + * _life is mandatory. + * + * e_entry always addresses _start. + * + * If .init exists: + * + * .init + * _start: + * ... + * END + * + * .text + * _life: + * ... + * + * then: + * + * e_entry = PTR(PROGRAM, _start) + * e_init_entry = PTR(PROGRAM, _start) + * e_life_entry = PTR(PROGRAM, _life) + * + * If .init does not exist, _start and _life MUST be consecutive labels + * at exactly the same .text address: + * + * .text + * _start: + * _life: + * ... + * + * then: + * + * e_init_entry = 0 + * e_entry == e_life_entry + * + * .fini/_fini are optional: + * + * e_fini_entry = PTR(PROGRAM, _fini), or zero when absent. + * + * main() is never a MELF entry point. The loader/runtime constructs the + * startup area containing argc/argv before _start. Startup data remains + * valid for the program lifetime. _life constructs the ordinary MCPU + * CALL-ABI argument block and calls main(argc, argv). + * + * MELF_ET_REL has no final lifecycle addresses. Therefore e_entry, + * e_init_entry, e_life_entry and e_fini_entry are all zero in ET_REL. + */ + + +/* + * MELF32 file header. + * + * Serialized ABI size: 64 bytes. + */ +typedef struct +{ + __mpu_uint8_t e_ident[MELF_EI_NIDENT]; + + MElf32_Half e_type; + MElf32_Half e_machine; + MElf32_Word e_version; + + MElf32_Addr e_entry; + MElf32_Addr e_init_entry; + MElf32_Addr e_life_entry; + MElf32_Addr e_fini_entry; + + MElf32_Off e_phoff; + MElf32_Off e_shoff; + + MElf32_Word e_flags; + + MElf32_Half e_ehsize; + MElf32_Half e_phentsize; + MElf32_Half e_phnum; + MElf32_Half e_shentsize; + MElf32_Half e_shnum; + MElf32_Half e_shstrndx; + +} MElf32_Ehdr; + + +/* + * MELF64 file header. + * + * Serialized ABI size: 88 bytes. + * + * MElf64_Addr is a 64-bit MCPU architectural address. For executable + * lifecycle entry fields it contains a full PTR64(PROGRAM, offset), not a + * raw PROGRAM offset. Therefore zero is available as an unambiguous + * "entry absent" value, while PTR64(PROGRAM, 0) is nonzero. + * + * Current ABI v1 uses MELF64. + */ +typedef struct +{ + __mpu_uint8_t e_ident[MELF_EI_NIDENT]; + + MElf64_Half e_type; + MElf64_Half e_machine; + MElf64_Word e_version; + + MElf64_Addr e_entry; + MElf64_Addr e_init_entry; + MElf64_Addr e_life_entry; + MElf64_Addr e_fini_entry; + + MElf64_Off e_phoff; + MElf64_Off e_shoff; + + MElf64_Word e_flags; + + MElf64_Half e_ehsize; + MElf64_Half e_phentsize; + MElf64_Half e_phnum; + MElf64_Half e_shentsize; + MElf64_Half e_shnum; + MElf64_Half e_shstrndx; + +} MElf64_Ehdr; + + +/* + * Serialized file-header sizes. + * + * Use these constants for e_ehsize and file I/O. Do not use sizeof() + * as the definition of the on-disk ABI. + */ +#define MELF32_EHDR_SIZE 64U +#define MELF64_EHDR_SIZE 88U + + +/* MELF32 file-header serialized offsets. */ +#define MELF32_EHDR_E_IDENT_OFF 0U +#define MELF32_EHDR_E_TYPE_OFF 16U +#define MELF32_EHDR_E_MACHINE_OFF 18U +#define MELF32_EHDR_E_VERSION_OFF 20U +#define MELF32_EHDR_E_ENTRY_OFF 24U +#define MELF32_EHDR_E_INIT_ENTRY_OFF 28U +#define MELF32_EHDR_E_LIFE_ENTRY_OFF 32U +#define MELF32_EHDR_E_FINI_ENTRY_OFF 36U +#define MELF32_EHDR_E_PHOFF_OFF 40U +#define MELF32_EHDR_E_SHOFF_OFF 44U +#define MELF32_EHDR_E_FLAGS_OFF 48U +#define MELF32_EHDR_E_EHSIZE_OFF 52U +#define MELF32_EHDR_E_PHENTSIZE_OFF 54U +#define MELF32_EHDR_E_PHNUM_OFF 56U +#define MELF32_EHDR_E_SHENTSIZE_OFF 58U +#define MELF32_EHDR_E_SHNUM_OFF 60U +#define MELF32_EHDR_E_SHSTRNDX_OFF 62U + + +/* MELF64 file-header serialized offsets. */ +#define MELF64_EHDR_E_IDENT_OFF 0U +#define MELF64_EHDR_E_TYPE_OFF 16U +#define MELF64_EHDR_E_MACHINE_OFF 18U +#define MELF64_EHDR_E_VERSION_OFF 20U +#define MELF64_EHDR_E_ENTRY_OFF 24U +#define MELF64_EHDR_E_INIT_ENTRY_OFF 32U +#define MELF64_EHDR_E_LIFE_ENTRY_OFF 40U +#define MELF64_EHDR_E_FINI_ENTRY_OFF 48U +#define MELF64_EHDR_E_PHOFF_OFF 56U +#define MELF64_EHDR_E_SHOFF_OFF 64U +#define MELF64_EHDR_E_FLAGS_OFF 72U +#define MELF64_EHDR_E_EHSIZE_OFF 76U +#define MELF64_EHDR_E_PHENTSIZE_OFF 78U +#define MELF64_EHDR_E_PHNUM_OFF 80U +#define MELF64_EHDR_E_SHENTSIZE_OFF 82U +#define MELF64_EHDR_E_SHNUM_OFF 84U +#define MELF64_EHDR_E_SHSTRNDX_OFF 86U + + +/* + * -------------------------------------------------------------------------- + * MELF section ABI + * -------------------------------------------------------------------------- + */ + +/* + * Section types. + * + * MCPU address-space semantics are NOT encoded by inventing one sh_type + * for each architectural segment. sh_type describes the physical nature + * of section contents; architectural placement is encoded by sh_flags. + * + * Only section types required by the current static object/executable + * model are declared here. Their detailed symbol/relocation semantics + * are defined by later ABI stages. + */ +#define MELF_SHT_NULL 0U +#define MELF_SHT_PROGBITS 1U +#define MELF_SHT_SYMTAB 2U +#define MELF_SHT_STRTAB 3U +#define MELF_SHT_RELA 4U +#define MELF_SHT_DYNAMIC 6U +#define MELF_SHT_NOBITS 8U +#define MELF_SHT_DYNSYM 11U + +/* + * Processor-specific section-type range. + * + * MELF is a private MCPU ELF-derived format, so these assignments are + * stable MELF/MCPU ABI values and do not claim ordinary ELF registry + * assignments. + */ +#define MELF_SHT_LOPROC 0x70000000U +#define MELF_SHT_MCPU_TARGET 0x70000000U +#define MELF_SHT_MCPU_LIFECYCLE 0x70000001U +#define MELF_SHT_HIPROC 0x7fffffffU + + +/* + * Standard section attribute flags used by MELF. + * + * MELF_SHF_WRITE means writable by the guest CPU. Host/runtime write + * access is independent of this flag. Thus CONTROL and INPUT do not + * carry MELF_SHF_WRITE even though the host runtime may provide/update + * their contents at lifecycle synchronization points. + */ +#define MELF_SHF_WRITE 0x00000001ULL +#define MELF_SHF_ALLOC 0x00000002ULL +#define MELF_SHF_EXECINSTR 0x00000004ULL + + +/* + * MCPU architectural segment field in sh_flags. + * + * Bits 20..23 : PTR64 segment selector. + * Bit 24 : segment field is valid. + * + * These bits live in the OSABI-specific flag range and fit identically + * in both MELF32 and MELF64 sh_flags. + * + * A separate VALID bit is required because DATA has selector zero. + * Without it, a DATA section could not be distinguished from a section + * with no guest architectural address at all (.symtab, .strtab, ...). + */ +#define MELF_SHF_MCPU_SEG_SHIFT 20U +#define MELF_SHF_MCPU_SEG_MASK 0x00f00000ULL +#define MELF_SHF_MCPU_SEG_VALID 0x01000000ULL + +#define MELF_SHF_MCPU_SEG(seg) \ + (MELF_SHF_MCPU_SEG_VALID | \ + ((((MElf64_Xword)(seg)) & 0x0fULL) << MELF_SHF_MCPU_SEG_SHIFT)) + +#define MELF_SHF_MCPU_HAS_SEG(flags) \ + ((((MElf64_Xword)(flags)) & MELF_SHF_MCPU_SEG_VALID) != 0) + +#define MELF_SHF_MCPU_GET_SEG(flags) \ + ((unsigned)((((MElf64_Xword)(flags)) & MELF_SHF_MCPU_SEG_MASK) >> \ + MELF_SHF_MCPU_SEG_SHIFT)) + + +/* + * MCPU PTR64 architectural segment selectors. + * + * STACK and HEAP are architectural spaces but ABI-v1 object files do not + * contain allocated STACK/HEAP payload sections. They are created by + * the runtime. Selectors 0x8..0xf remain reserved and cannot be used by + * ABI-v1 allocated sections. + */ +#define MELF_MCPU_SEG_DATA 0x0U +#define MELF_MCPU_SEG_STACK 0x1U +#define MELF_MCPU_SEG_PROGRAM 0x2U +#define MELF_MCPU_SEG_CONTROL 0x3U +#define MELF_MCPU_SEG_INPUT 0x4U +#define MELF_MCPU_SEG_OUTPUT 0x5U +#define MELF_MCPU_SEG_TRACE 0x6U +#define MELF_MCPU_SEG_HEAP 0x7U + + +/* Convenient encoded segment flags. */ +#define MELF_SHF_MCPU_DATA MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_DATA) +#define MELF_SHF_MCPU_STACK MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_STACK) +#define MELF_SHF_MCPU_PROGRAM MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_PROGRAM) +#define MELF_SHF_MCPU_CONTROL MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_CONTROL) +#define MELF_SHF_MCPU_INPUT MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_INPUT) +#define MELF_SHF_MCPU_OUTPUT MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_OUTPUT) +#define MELF_SHF_MCPU_TRACE MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_TRACE) +#define MELF_SHF_MCPU_HEAP MELF_SHF_MCPU_SEG(MELF_MCPU_SEG_HEAP) + + +/* + * Canonical ABI-v1 flags for the architectural sections. + * + * No custom SHT_MCPU_* type is required for these spaces. + */ +#define MELF_SHFLAGS_INIT \ + (MELF_SHF_ALLOC | MELF_SHF_EXECINSTR | MELF_SHF_MCPU_PROGRAM) + +#define MELF_SHFLAGS_TEXT \ + (MELF_SHF_ALLOC | MELF_SHF_EXECINSTR | MELF_SHF_MCPU_PROGRAM) + +#define MELF_SHFLAGS_FINI \ + (MELF_SHF_ALLOC | MELF_SHF_EXECINSTR | MELF_SHF_MCPU_PROGRAM) + +#define MELF_SHFLAGS_DATA \ + (MELF_SHF_ALLOC | MELF_SHF_WRITE | MELF_SHF_MCPU_DATA) + +#define MELF_SHFLAGS_BSS \ + (MELF_SHF_ALLOC | MELF_SHF_WRITE | MELF_SHF_MCPU_DATA) + +#define MELF_SHFLAGS_CONTROL \ + (MELF_SHF_ALLOC | MELF_SHF_MCPU_CONTROL) + +#define MELF_SHFLAGS_INPUT \ + (MELF_SHF_ALLOC | MELF_SHF_MCPU_INPUT) + +#define MELF_SHFLAGS_OUTPUT \ + (MELF_SHF_ALLOC | MELF_SHF_WRITE | MELF_SHF_MCPU_OUTPUT) + +#define MELF_SHFLAGS_TRACE \ + (MELF_SHF_ALLOC | MELF_SHF_WRITE | MELF_SHF_MCPU_TRACE) + + +/* + * Canonical section names. + * + * Names identify the conventional role of a section for the assembler + * and linker. The authoritative architectural address space is encoded + * in sh_flags, not inferred from the string name by the loader. + */ +#define MELF_SEC_INIT ".init" +#define MELF_SEC_TEXT ".text" +#define MELF_SEC_FINI ".fini" + +#define MELF_SEC_DATA ".data" +#define MELF_SEC_BSS ".bss" +#define MELF_SEC_CONTROL ".control" +#define MELF_SEC_INPUT ".input" +#define MELF_SEC_OUTPUT ".output" +#define MELF_SEC_TRACE ".trace" + +#define MELF_SEC_SYMTAB ".symtab" +#define MELF_SEC_STRTAB ".strtab" +#define MELF_SEC_SHSTRTAB ".shstrtab" +#define MELF_SEC_DYNSYM ".dynsym" +#define MELF_SEC_DYNSTR ".dynstr" +#define MELF_SEC_DYNAMIC ".dynamic" + +#define MELF_SEC_MCPU_TARGET ".mcpu.target" +#define MELF_SEC_MCPU_LIFECYCLE ".mcpu.lifecycle" + + +/* + * Section-header semantics. + * + * ET_REL: + * + * - sh_addr is zero for all sections because final architectural + * addresses have not yet been assigned. + * - each allocated input section contribution carries its target + * architectural segment in sh_flags; + * - sh_addralign carries the maximum required guest alignment of that + * contribution; + * - relocatable DATA has no architectural 16-byte NULL guard. + * + * ET_EXEC: + * + * - every MELF_SHF_ALLOC section has a final full architectural address + * in sh_addr: + * + * PTR64(segment, final_offset) + * + * - .init/.text/.fini are separate sections, but all map to the single + * PROGRAM architectural space (selector 0x2); + * - .data/.bss map to DATA; + * - .control maps to CONTROL; + * - .input maps to INPUT; + * - .output maps to OUTPUT; + * - .trace maps to TRACE; + * - STACK and HEAP do not have object payload sections in ABI v1. + * + * sh_addralign is a guest-address alignment requirement. Values zero + * and one mean no alignment requirement; otherwise the value must be a + * power of two. + * + * SHT_NOBITS sections occupy architectural memory (sh_size bytes) but + * have no payload bytes in the MELF file. + */ + + +/* + * MELF32 section header. + * + * Serialized ABI size: 40 bytes. + */ +typedef struct +{ + MElf32_Word sh_name; + MElf32_Word sh_type; + MElf32_Word sh_flags; + MElf32_Addr sh_addr; + MElf32_Off sh_offset; + MElf32_Word sh_size; + MElf32_Word sh_link; + MElf32_Word sh_info; + MElf32_Word sh_addralign; + MElf32_Word sh_entsize; + +} MElf32_Shdr; + + +/* + * MELF64 section header. + * + * Serialized ABI size: 64 bytes. + */ +typedef struct +{ + MElf64_Word sh_name; + MElf64_Word sh_type; + MElf64_Xword sh_flags; + MElf64_Addr sh_addr; + MElf64_Off sh_offset; + MElf64_Xword sh_size; + MElf64_Word sh_link; + MElf64_Word sh_info; + MElf64_Xword sh_addralign; + MElf64_Xword sh_entsize; + +} MElf64_Shdr; + + +#define MELF32_SHDR_SIZE 40U +#define MELF64_SHDR_SIZE 64U + + +/* MELF32 section-header serialized offsets. */ +#define MELF32_SHDR_SH_NAME_OFF 0U +#define MELF32_SHDR_SH_TYPE_OFF 4U +#define MELF32_SHDR_SH_FLAGS_OFF 8U +#define MELF32_SHDR_SH_ADDR_OFF 12U +#define MELF32_SHDR_SH_OFFSET_OFF 16U +#define MELF32_SHDR_SH_SIZE_OFF 20U +#define MELF32_SHDR_SH_LINK_OFF 24U +#define MELF32_SHDR_SH_INFO_OFF 28U +#define MELF32_SHDR_SH_ADDRALIGN_OFF 32U +#define MELF32_SHDR_SH_ENTSIZE_OFF 36U + + +/* MELF64 section-header serialized offsets. */ +#define MELF64_SHDR_SH_NAME_OFF 0U +#define MELF64_SHDR_SH_TYPE_OFF 4U +#define MELF64_SHDR_SH_FLAGS_OFF 8U +#define MELF64_SHDR_SH_ADDR_OFF 16U +#define MELF64_SHDR_SH_OFFSET_OFF 24U +#define MELF64_SHDR_SH_SIZE_OFF 32U +#define MELF64_SHDR_SH_LINK_OFF 40U +#define MELF64_SHDR_SH_INFO_OFF 44U +#define MELF64_SHDR_SH_ADDRALIGN_OFF 48U +#define MELF64_SHDR_SH_ENTSIZE_OFF 56U + + +/* + * -------------------------------------------------------------------------- + * MELF symbol ABI + * -------------------------------------------------------------------------- + */ + +/* + * Symbol bindings. + * + * Ordinary assembler labels are local by default. + * + * .global name + * .globl name + * + * make a defined symbol GLOBAL. + * + * .extern name + * + * creates/requires a GLOBAL undefined symbol. + * + * A future/current assembler .weak directive may mark a symbol WEAK. + * Lifecycle symbols _start, _life and _fini may not be WEAK. + */ +#define MELF_STB_LOCAL 0U +#define MELF_STB_GLOBAL 1U +#define MELF_STB_WEAK 2U + + +/* + * Symbol types. + * + * Ordinary assembler labels may remain STT_NOTYPE when no stronger type + * information is available. Compiler/toolchain generated symbols should + * use STT_FUNC or STT_OBJECT when their semantic kind is known. + * + * Lifecycle symbols _start, _life and _fini are always STT_FUNC. + */ +#define MELF_STT_NOTYPE 0U +#define MELF_STT_OBJECT 1U +#define MELF_STT_FUNC 2U +#define MELF_STT_SECTION 3U +#define MELF_STT_FILE 4U + + +/* + * Symbol visibility. + * + * ABI versions 1 and 2 use MELF_STV_DEFAULT for ordinary link-visible + * symbols. ABI-v2 startup interposition currently exports/preempts only + * DEFAULT GLOBAL/WEAK names. The remaining visibility values stay reserved. + */ +#define MELF_STV_DEFAULT 0U +#define MELF_STV_INTERNAL 1U +#define MELF_STV_HIDDEN 2U +#define MELF_STV_PROTECTED 3U + +#define MELF_ST_VISIBILITY(other) \ + ((unsigned)((other) & 0x03U)) + + +/* + * st_info encoding. + * + * Binding occupies the high four bits and type the low four bits. + */ +#define MELF32_ST_BIND(info) \ + ((unsigned)(((__mpu_uint8_t)(info)) >> 4)) + +#define MELF32_ST_TYPE(info) \ + ((unsigned)(((__mpu_uint8_t)(info)) & 0x0fU)) + +#define MELF32_ST_INFO(bind, type) \ + ((__mpu_uint8_t)((((unsigned)(bind) & 0x0fU) << 4) | \ + ((unsigned)(type) & 0x0fU))) + +#define MELF64_ST_BIND(info) MELF32_ST_BIND(info) +#define MELF64_ST_TYPE(info) MELF32_ST_TYPE(info) +#define MELF64_ST_INFO(bind, type) \ + MELF32_ST_INFO((bind), (type)) + + +/* + * Special section indices used by st_shndx. + */ +#define MELF_SHN_UNDEF 0x0000U +#define MELF_SHN_LORESERVE 0xff00U +#define MELF_SHN_ABS 0xfff1U +#define MELF_SHN_COMMON 0xfff2U +#define MELF_SHN_HIRESERVE 0xffffU + + +/* + * Symbol table entry zero. + * + * Symbol index zero is the undefined symbol and must contain all-zero + * fields. Therefore st_name == 0 refers to the empty string in the + * associated string table. + */ +#define MELF_STN_UNDEF 0U + + +/* + * Canonical lifecycle symbol names. + */ +#define MELF_SYM_START "_start" +#define MELF_SYM_LIFE "_life" +#define MELF_SYM_FINI "_fini" + +/* + * Assembler-only lifecycle convenience pseudo-label. + * + * This is source syntax, not an additional MELF symbol. + */ +#define MELF_ASM_PSEUDO_START_LIFE "_start_life" + + +/* + * MELF32 symbol table entry. + * + * Serialized ABI size: 16 bytes. + */ +typedef struct +{ + MElf32_Word st_name; + MElf32_Addr st_value; + MElf32_Word st_size; + __mpu_uint8_t st_info; + __mpu_uint8_t st_other; + MElf32_Half st_shndx; + +} MElf32_Sym; + + +/* + * MELF64 symbol table entry. + * + * Serialized ABI size: 24 bytes. + */ +typedef struct +{ + MElf64_Word st_name; + __mpu_uint8_t st_info; + __mpu_uint8_t st_other; + MElf64_Half st_shndx; + MElf64_Addr st_value; + MElf64_Xword st_size; + +} MElf64_Sym; + + +#define MELF32_SYM_SIZE 16U +#define MELF64_SYM_SIZE 24U + + +/* MELF32 symbol-entry serialized offsets. */ +#define MELF32_SYM_ST_NAME_OFF 0U +#define MELF32_SYM_ST_VALUE_OFF 4U +#define MELF32_SYM_ST_SIZE_OFF 8U +#define MELF32_SYM_ST_INFO_OFF 12U +#define MELF32_SYM_ST_OTHER_OFF 13U +#define MELF32_SYM_ST_SHNDX_OFF 14U + + +/* MELF64 symbol-entry serialized offsets. */ +#define MELF64_SYM_ST_NAME_OFF 0U +#define MELF64_SYM_ST_INFO_OFF 4U +#define MELF64_SYM_ST_OTHER_OFF 5U +#define MELF64_SYM_ST_SHNDX_OFF 6U +#define MELF64_SYM_ST_VALUE_OFF 8U +#define MELF64_SYM_ST_SIZE_OFF 16U + + +/* + * Symbol-table section rules. + * + * A SHT_SYMTAB section: + * + * sh_link = section index of the associated SHT_STRTAB (.strtab); + * sh_info = symbol-table index of the first non-local symbol; + * sh_entsize = MELF32_SYM_SIZE or MELF64_SYM_SIZE. + * + * All STB_LOCAL symbols MUST precede STB_GLOBAL/STB_WEAK symbols. + * + * String-table offset zero contains an empty NUL byte. st_name is an + * offset into the associated string table and symbol names are + * case-sensitive. + */ + + +/* + * st_value semantics. + * + * MELF_ET_REL: + * + * - defined ordinary symbol: + * st_shndx = defining section index + * st_value = byte offset from the beginning of that section + * + * - undefined symbol: + * st_shndx = MELF_SHN_UNDEF + * st_value = 0 + * + * - STT_SECTION symbol: + * st_value = 0 + * + * MELF_ET_EXEC: + * + * For a symbol defined in an allocated MCPU architectural section: + * + * st_value = PTR64(segment, final_offset) + * + * Thus a .text/.init/.fini function has a PROGRAM PTR64 value, + * a .data/.bss object has a DATA PTR64 value, .control has a CONTROL + * PTR64 value, and so on. + * + * Symbols in non-allocated metadata sections have no guest runtime + * address in ABI version 1 and must use st_value == 0 unless a later + * ABI explicitly defines another meaning. + * + * st_size is the object/function size in bytes when known. Zero means + * that no size is supplied by this symbol entry. + */ + + +/* + * Absolute and COMMON symbols. + * + * Assembler .equ symbols are resolved completely by the assembler and + * do NOT enter the relocatable symbol namespace. Therefore current + * mcpu-as does not emit .equ definitions as MELF_SHN_ABS symbols. + * + * MELF_SHN_COMMON is reserved for format compatibility. COMMON symbol + * allocation semantics are not part of MCPU ABI version 1 and a v1 + * linker must reject input requiring COMMON allocation. + */ + + +/* + * Binding and resolution rules. + * + * LOCAL: + * visible only inside its own relocatable object; never participates + * in cross-object name resolution. + * + * GLOBAL: + * a defined GLOBAL symbol is exported for static link resolution. + * an undefined GLOBAL symbol must be satisfied by another input object; + * otherwise the static link fails. + * + * WEAK: + * a strong GLOBAL definition overrides WEAK definitions of the same + * name. If only multiple WEAK definitions exist, mcpu-ld selects the + * first definition in deterministic linker input order. + * + * An unresolved WEAK symbol has symbol value zero. Whether a specific + * relocation kind may encode that unresolved zero value without + * ambiguity is defined by the relocation ABI (specification stage 5). + * + * Multiple strong GLOBAL definitions of one name are a link error. + */ + + +/* + * Lifecycle symbol ABI. + * + * _start and _life are mandatory in every MELF_ET_EXEC. + * _fini is optional. + * + * These symbols are automatically: + * + * binding = MELF_STB_GLOBAL + * type = MELF_STT_FUNC + * + * They cannot be LOCAL or WEAK. + * + * With .init: + * + * _start is defined in .init + * _life is defined in .text + * + * e_entry = st_value(_start) + * e_init_entry = st_value(_start) + * e_life_entry = st_value(_life) + * + * Without .init: + * + * _start and _life are defined in .text as consecutive labels with + * exactly the same byte offset: + * + * _start: + * _life: + * + * e_init_entry = 0 + * e_entry = st_value(_start) + * e_life_entry = st_value(_life) + * e_entry == e_life_entry + * + * If .fini is present, _fini is defined in .fini and: + * + * e_fini_entry = st_value(_fini) + * + * Otherwise: + * + * e_fini_entry = 0 + * + * main() has no special MELF symbol status and is never a MELF entry + * point. It is an ordinary function symbol. _life constructs the + * normal CALL-ABI argument block and calls main(argc, argv). + */ + + +/* + * -------------------------------------------------------------------------- + * MELF ABI-v3 dynamic dependency metadata + * -------------------------------------------------------------------------- + * + * ABI-v3 dynamic ET_EXEC/ET_DYN files contain exactly one .dynamic section: + * + * sh_type = MELF_SHT_DYNAMIC + * sh_flags = 0 + * sh_link = section index of .dynstr + * sh_info = 0 + * sh_addralign = 8 + * sh_entsize = MELF64_DYN_SIZE + * + * .dynamic is non-ALLOC startup metadata. Every entry is a signed tag and + * an unsigned value. MELF_DT_NEEDED and MELF_DT_SONAME values are byte + * offsets into the .dynstr section named by sh_link. + * + * The canonical stream ends with exactly one MELF_DT_NULL entry and has no + * entries after it. ABI-v3 ET_DYN used as a normal shared library has exactly + * one MELF_DT_SONAME. ET_EXEC must not contain MELF_DT_SONAME. Both ET_EXEC + * and ET_DYN may contain zero or more MELF_DT_NEEDED entries. + * + * ABI-v3 is intentionally a development ABI until explicitly frozen. + */ +#define MELF_DT_NULL 0 +#define MELF_DT_NEEDED 1 +#define MELF_DT_SONAME 14 + +typedef struct +{ + MElf64_Sxword d_tag; + MElf64_Xword d_val; + +} MElf64_Dyn; + +#define MELF64_DYN_SIZE 16U +#define MELF64_DYN_D_TAG_OFF 0U +#define MELF64_DYN_D_VAL_OFF 8U + + +/* + * -------------------------------------------------------------------------- + * MELF RELA relocation ABI + * -------------------------------------------------------------------------- + * + * MCPU uses RELA-style relocations only. The relocation addend is stored + * explicitly in the relocation record and is never recovered implicitly + * from the field being relocated. + * + * The current MCPU ABI was deliberately defined before MELF and has + * exactly five semantic relocation kinds: + * + * CODE64 + * DATA64 + * PTR64_CODE + * PTR64_DATA + * CPTR64_DATA + * + * MELF maps them one-to-one to machine-specific relocation numbers. + */ + + +/* + * Relocation types. + * + * Numeric values are part of MELF/MCPU ABI version 1. + */ +#define MELF_R_MCPU_NONE 0U +#define MELF_R_MCPU_CODE64 1U +#define MELF_R_MCPU_DATA64 2U +#define MELF_R_MCPU_PTR64_CODE 3U +#define MELF_R_MCPU_PTR64_DATA 4U +#define MELF_R_MCPU_CPTR64_DATA 5U + + +/* + * r_info encoding. + * + * MELF32 follows the conventional ELF32 split: + * + * bits 31..8 : symbol-table index + * bits 7..0 : relocation type + * + * MELF64 follows the conventional ELF64 split: + * + * bits 63..32 : symbol-table index + * bits 31..0 : relocation type + * + * MCPU ABI version 1 is MELF64. MELF32 remains reserved for a possible + * future 32-bit ABI. + */ +#define MELF32_R_SYM(info) \ + ((MElf32_Word)(((MElf32_Word)(info)) >> 8)) + +#define MELF32_R_TYPE(info) \ + ((MElf32_Word)(((MElf32_Word)(info)) & 0xffU)) + +#define MELF32_R_INFO(sym, type) \ + ((MElf32_Word)((((MElf32_Word)(sym)) << 8) | \ + (((MElf32_Word)(type)) & 0xffU))) + + +#define MELF64_R_SYM(info) \ + ((MElf64_Word)(((MElf64_Xword)(info)) >> 32)) + +#define MELF64_R_TYPE(info) \ + ((MElf64_Word)(((MElf64_Xword)(info)) & 0xffffffffULL)) + +#define MELF64_R_INFO(sym, type) \ + ((MElf64_Xword)((((MElf64_Xword)(sym)) << 32) | \ + (((MElf64_Xword)(type)) & 0xffffffffULL))) + + +/* + * MELF32 RELA relocation entry. + * + * This structure is reserved for a possible future MELF32 ABI. + * MCPU ABI version 1 does not accept MELF32 files. + * + * Serialized size: 12 bytes. + */ +typedef struct +{ + MElf32_Addr r_offset; + MElf32_Word r_info; + MElf32_Sword r_addend; + +} MElf32_Rela; + + +/* + * MELF64 RELA relocation entry. + * + * r_addend is a signed 64-bit addend, matching the already-defined MCPU + * relocatable expression: + * + * SYMBOL + signed_constant + * + * Serialized size: 24 bytes. + */ +typedef struct +{ + MElf64_Addr r_offset; + MElf64_Xword r_info; + MElf64_Sxword r_addend; + +} MElf64_Rela; + + +#define MELF32_RELA_SIZE 12U +#define MELF64_RELA_SIZE 24U + + +/* MELF32 RELA serialized offsets. */ +#define MELF32_RELA_R_OFFSET_OFF 0U +#define MELF32_RELA_R_INFO_OFF 4U +#define MELF32_RELA_R_ADDEND_OFF 8U + + +/* MELF64 RELA serialized offsets. */ +#define MELF64_RELA_R_OFFSET_OFF 0U +#define MELF64_RELA_R_INFO_OFF 8U +#define MELF64_RELA_R_ADDEND_OFF 16U + + +/* + * Canonical relocation section names used by mcpu-as/mcpu-ld. + * + * The name is conventional; SHT_RELA together with sh_info/sh_link is + * authoritative. + */ +#define MELF_SEC_RELA_INIT ".rela.init" +#define MELF_SEC_RELA_TEXT ".rela.text" +#define MELF_SEC_RELA_FINI ".rela.fini" + +#define MELF_SEC_RELA_DATA ".rela.data" +#define MELF_SEC_RELA_CONTROL ".rela.control" +#define MELF_SEC_RELA_INPUT ".rela.input" +#define MELF_SEC_RELA_OUTPUT ".rela.output" +#define MELF_SEC_RELA_TRACE ".rela.trace" + + +/* + * SHT_RELA section rules. + * + * For every SHT_RELA section: + * + * sh_link = section index of the associated SHT_SYMTAB; + * sh_info = section index of the section whose contents are patched; + * sh_entsize = MELF32_RELA_SIZE or MELF64_RELA_SIZE. + * + * In MELF_ET_REL: + * + * r_offset is the byte offset, from the beginning of the section named + * by sh_info, of the first byte of the 8-byte relocation field. + * + * MELF_R_SYM(r_info) is an index into the symbol table named by + * sh_link. + * + * MELF_R_TYPE(r_info) selects one of the MCPU relocation kinds below. + * + * Every ABI-v1 MCPU relocation patches exactly 8 bytes. + * + * The complete relocation field [r_offset, r_offset + 8) must lie inside + * the target section. Relocations into SHT_NOBITS contents are invalid + * in ABI version 1 because there are no stored bytes to patch. + * + * In a final MELF_ET_EXEC produced by the static linker all required + * link-time relocations have already been applied. ABI version 1 defines + * no dynamic/runtime relocation mechanism. A runtime loader therefore + * must not be required to perform these relocations. + */ + + +/* + * Relocation expression. + * + * The only relocatable expression represented by an ABI-v1 RELA record is + * + * S + A + * + * where: + * + * S = final byte offset of the referenced symbol within its + * architectural MCPU segment; + * + * A = signed r_addend. + * + * For a symbol defined in input object O: + * + * S + A = + * + * output_base(O, symbol_section) + * + symbol_section_relative_offset + * + r_addend + * + * No ABI-v1 relocation represents: + * + * symbol1 - symbol2 + * symbol1 + symbol2 + * k * symbol + * + * except where mcpu-as can reduce such an expression completely to an + * absolute constant before emitting the object file. + * + * The linker must evaluate addition with overflow detection. Pointer + * relocations additionally require the resulting byte offset to fit the + * 60-bit PTR64 offset field. + */ + + +/* + * Relocation serialization domains. + * + * There are two independent byte-order domains: + * + * PROGRAM machine-field domain + * canonical MSB-first, independent of EI_DATA; + * + * architectural data-object domain + * target object representation described by the MELF target profile. + * + * The relocation kind determines which domain is used to write the + * resulting 8-byte value. + */ + + +/* + * MELF_R_MCPU_CODE64 + * ------------------ + * + * Relocation field: + * PROGRAM section only. + * + * Referenced symbol: + * must resolve to PROGRAM. + * + * Value written: + * + * final PROGRAM byte offset = S + A + * + * This is a raw CODE64 offset, NOT a PTR64. + * + * Serialization: + * exactly 8 bytes, canonical MSB-first. + * + * Typical users: + * direct JMP/CALL and other machine-code fields containing CODE64. + */ +#define MELF_R_MCPU_CODE64_FIELD_SIZE 8U + + +/* + * MELF_R_MCPU_DATA64 + * ------------------ + * + * Relocation field: + * PROGRAM section only. + * + * Referenced symbol: + * must resolve to DATA. + * + * Value written: + * + * final DATA byte offset = S + A + * + * This is a raw ADDR64/DATA offset, NOT a PTR64. + * + * Serialization: + * exactly 8 bytes, canonical MSB-first. + * + * Typical users: + * direct LOAD/STORE fields containing ADDR64. + */ +#define MELF_R_MCPU_DATA64_FIELD_SIZE 8U + + +/* + * MELF_R_MCPU_PTR64_CODE + * ---------------------- + * + * Relocation field: + * PROGRAM section only. + * + * Referenced symbol: + * may resolve to an image-backed architectural section in: + * + * DATA + * PROGRAM + * CONTROL + * INPUT + * OUTPUT + * TRACE + * + * Value written: + * + * PTR64(target_segment, S + A) + * + * Serialization: + * exactly 8 bytes, canonical MSB-first. + * + * Typical user: + * LEA immediate fields. + */ +#define MELF_R_MCPU_PTR64_CODE_FIELD_SIZE 8U + + +/* + * MELF_R_MCPU_PTR64_DATA + * ---------------------- + * + * Relocation field: + * stored data-object section only: + * + * DATA + * CONTROL + * INPUT + * OUTPUT + * TRACE + * + * Referenced symbol: + * may resolve to: + * + * DATA + * PROGRAM + * CONTROL + * INPUT + * OUTPUT + * TRACE + * + * Value written: + * + * PTR64(target_segment, S + A) + * + * Serialization: + * exactly 8 bytes in the target architectural uint64 object + * representation, NOT canonical PROGRAM MSB-first. + * + * Typical user: + * .ptr expression. + */ +#define MELF_R_MCPU_PTR64_DATA_FIELD_SIZE 8U + + +/* + * MELF_R_MCPU_CPTR64_DATA + * ----------------------- + * + * Relocation field: + * stored data-object section only: + * + * DATA + * CONTROL + * INPUT + * OUTPUT + * TRACE + * + * Referenced symbol: + * MUST resolve to PROGRAM. + * + * Value written: + * + * PTR64(PROGRAM, S + A) + * + * Serialization: + * exactly 8 bytes in the target architectural uint64 object + * representation. + * + * Typical user: + * .cptr expression. + * + * mcpu-ld must reject a target that resolves to any segment other than + * PROGRAM. + */ +#define MELF_R_MCPU_CPTR64_DATA_FIELD_SIZE 8U + + +/* + * Static target-segment restrictions. + * + * ABI-v1 relocatable objects have no STACK or HEAP payload and therefore + * no link-time symbol can denote a static STACK or HEAP object. + * + * Pointer relocations in ABI version 1 consequently may construct PTR64 + * values only for image-backed spaces: + * + * DATA, PROGRAM, CONTROL, INPUT, OUTPUT, TRACE. + * + * STACK, HEAP and reserved selectors 0x8..0xf are invalid relocation + * targets in ABI version 1. + */ + + +/* + * Unresolved WEAK symbols. + * + * For MELF_R_MCPU_CODE64 and MELF_R_MCPU_DATA64 an unresolved WEAK symbol + * is a link error. Raw offset zero is a real architectural offset and + * cannot represent "symbol absent". + * + * For: + * + * MELF_R_MCPU_PTR64_CODE + * MELF_R_MCPU_PTR64_DATA + * MELF_R_MCPU_CPTR64_DATA + * + * an unresolved WEAK symbol is permitted only when r_addend == 0. The + * linker writes the all-zero PTR64 value, i.e. architectural NULL. + * + * An unresolved WEAK pointer relocation with a non-zero addend is a link + * error; the linker must not manufacture a non-NULL pointer by adding an + * offset to an absent symbol. + */ + + +/* + * R_MCPU_NONE. + * + * MELF_R_MCPU_NONE performs no write. Its symbol index and addend have no + * relocation effect. Writers should normally emit symbol index zero and + * addend zero for this type. + */ + + +/* + * PC-relative relocations. + * + * ABI version 1 intentionally defines no PC-relative relocation. + * Current direct MCPU JMP/CALL operands contain absolute PROGRAM CODE64 + * byte offsets. A future relative-control-flow instruction, if added to + * the ISA, must receive a new explicit relocation kind rather than + * changing the semantics of MELF_R_MCPU_CODE64. + */ + + +/* + * -------------------------------------------------------------------------- + * MCPU target-profile metadata + * -------------------------------------------------------------------------- + * + * The target profile is deliberately separate from MELF EI_CLASS. + * + * EI_CLASS describes the MELF container/address class. The current MCPU + * ABI is MELF64 because architectural addresses, PC, SP, FP and PTR64 are + * 64-bit quantities. + * + * The programming/data model recorded here describes the configured MCPU/ + * LibMPU target data profile. It must never be inferred from the host on + * which mcpu-as, mcpu-ld or mcpu-run happen to execute. + * + * .mcpu.target is container metadata, not a guest data object. All its + * multi-byte fields are serialized according to EI_DATA. + */ + + +/* + * Target-profile record format version. + * + * This versions the layout/meaning of MElf_McpuTarget itself. It is + * independent of MELF_EV_CURRENT and EI_ABIVERSION. + */ +#define MELF_MCPU_TARGET_VERSION_NONE 0U +#define MELF_MCPU_TARGET_VERSION_1 1U +#define MELF_MCPU_TARGET_VERSION_CURRENT MELF_MCPU_TARGET_VERSION_1 + + +/* + * MCPU ISA and ABI revisions carried by target metadata. + * + * Revision 1 denotes the current architecture/ABI contract being encoded + * by this MELF specification. These are architecture revisions, not + * LibMCPU package release numbers such as 0.0.x. + */ +#define MELF_MCPU_ISA_REVISION_1 1U +#define MELF_MCPU_ISA_REVISION_CURRENT MELF_MCPU_ISA_REVISION_1 + +#define MELF_MCPU_ABI_REVISION_1 1U +#define MELF_MCPU_ABI_REVISION_CURRENT MELF_MCPU_ABI_REVISION_1 + + +/* + * Programming/data models. + * + * These names describe the target language/LibMPU scalar data model. + * They do not change the architectural MCPU PTR64 representation: + * MCPU ABI v1 still uses the 64-bit SEG:OFFSET pointer/address model + * described below. + */ +#define MELF_MCPU_MODEL_NONE 0U +#define MELF_MCPU_MODEL_ILP32 1U +#define MELF_MCPU_MODEL_LP64 2U +#define MELF_MCPU_MODEL_LLP64 3U + + +/* + * Target data byte order. + * + * Values intentionally match MELF_DATA_LSB/MELF_DATA_MSB. In every + * valid ABI-v1 file: + * + * mt_data_byte_order == e_ident[MELF_EI_DATA] + * + * This duplicate is a deliberate target-profile consistency check. + */ +#define MELF_MCPU_BYTE_ORDER_NONE 0U +#define MELF_MCPU_BYTE_ORDER_LSB MELF_DATA_LSB +#define MELF_MCPU_BYTE_ORDER_MSB MELF_DATA_MSB + + +/* + * Target data word order. + * + * This records the configured LibMPU word-order profile independently of + * byte order. mcpu-as/mcpu-ld/mcpu-run map these values to the configured + * MPU_WORD_ORDER semantics of the target toolchain. + */ +#define MELF_MCPU_WORD_ORDER_NONE 0U +#define MELF_MCPU_WORD_ORDER_LSB 1U +#define MELF_MCPU_WORD_ORDER_MSB 2U + + +/* Integer object representation. */ +#define MELF_MCPU_INTEGER_MODEL_NONE 0U +#define MELF_MCPU_INTEGER_MODEL_TWOS_COMPLEMENT 1U + + +/* + * Architectural address/pointer model. + * + * PTR64: + * bits 63..60 : architectural segment selector + * bits 59..0 : byte offset within the segment + */ +#define MELF_MCPU_ADDRESS_MODEL_NONE 0U +#define MELF_MCPU_ADDRESS_MODEL_PTR64_SEG4_OFF60 1U + + +/* + * Register-file model. + * + * REG24 identifies an overlapping view in the 512 KiB byte-addressable + * MCPU register file. + */ +#define MELF_MCPU_REGISTER_MODEL_NONE 0U +#define MELF_MCPU_REGISTER_MODEL_REG24_512K 1U + + +/* + * Real/Complex object representations. + * + * MCPU does not define an independent floating-point or complex storage + * format. ABI-v1 real and complex objects use the configured LibMPU + * external object representation. Complex component/half ordering + * follows the configured LibMPU word-order profile. + */ +#define MELF_MCPU_REAL_MODEL_NONE 0U +#define MELF_MCPU_REAL_MODEL_LIBMPU 1U + +#define MELF_MCPU_COMPLEX_MODEL_NONE 0U +#define MELF_MCPU_COMPLEX_MODEL_LIBMPU 1U + + +/* + * ABI-v1 natural maximum alignment for ordinary language objects. + * + * Larger explicit section/table alignment remains legal through + * sh_addralign; this constant is the normal object-layout ABI maximum. + */ +#define MELF_MCPU_ABI_MAX_ALIGN 16U + + +/* + * Target-profile flags / feature bitmap. + * + * No optional target-profile flags or required feature bits are assigned + * by ABI version 1 yet. Writers emit zero. Readers reject unknown + * required feature bits. + */ +#define MELF_MCPU_TARGET_FLAGS_NONE 0x00000000U +#define MELF_MCPU_REQUIRED_FEATURES_NONE 0x0000000000000000ULL + + +/* + * Fixed class-independent target-profile record. + * + * Serialized size: 64 bytes. + * + * This metadata record intentionally contains no host pointers and no + * MELF-class-sized address fields, so one stable layout is sufficient. + */ +typedef struct +{ + __mpu_uint32_t mt_version; + __mpu_uint32_t mt_size; + + __mpu_uint32_t mt_isa_revision; + __mpu_uint32_t mt_abi_revision; + + __mpu_uint32_t mt_program_model; + __mpu_uint32_t mt_data_byte_order; + __mpu_uint32_t mt_data_word_order; + __mpu_uint32_t mt_integer_model; + + __mpu_uint32_t mt_address_model; + __mpu_uint32_t mt_register_model; + __mpu_uint32_t mt_real_model; + __mpu_uint32_t mt_complex_model; + + __mpu_uint32_t mt_max_align; + __mpu_uint32_t mt_flags; + + __mpu_uint64_t mt_required_features; + +} MElf_McpuTarget; + + +#define MELF_MCPU_TARGET_SIZE 64U + +#define MELF_MCPU_TARGET_MT_VERSION_OFF 0U +#define MELF_MCPU_TARGET_MT_SIZE_OFF 4U +#define MELF_MCPU_TARGET_MT_ISA_REVISION_OFF 8U +#define MELF_MCPU_TARGET_MT_ABI_REVISION_OFF 12U +#define MELF_MCPU_TARGET_MT_PROGRAM_MODEL_OFF 16U +#define MELF_MCPU_TARGET_MT_DATA_BYTE_ORDER_OFF 20U +#define MELF_MCPU_TARGET_MT_DATA_WORD_ORDER_OFF 24U +#define MELF_MCPU_TARGET_MT_INTEGER_MODEL_OFF 28U +#define MELF_MCPU_TARGET_MT_ADDRESS_MODEL_OFF 32U +#define MELF_MCPU_TARGET_MT_REGISTER_MODEL_OFF 36U +#define MELF_MCPU_TARGET_MT_REAL_MODEL_OFF 40U +#define MELF_MCPU_TARGET_MT_COMPLEX_MODEL_OFF 44U +#define MELF_MCPU_TARGET_MT_MAX_ALIGN_OFF 48U +#define MELF_MCPU_TARGET_MT_FLAGS_OFF 52U +#define MELF_MCPU_TARGET_MT_REQUIRED_FEATURES_OFF 56U + + +/* + * .mcpu.target section rules. + * + * ET_REL: + * exactly one .mcpu.target section is mandatory. + * + * ET_EXEC: + * exactly one .mcpu.target section is mandatory. + * + * The section has: + * + * sh_type = MELF_SHT_MCPU_TARGET + * sh_flags = 0 + * sh_addr = 0 + * sh_size = MELF_MCPU_TARGET_SIZE + * sh_link = 0 + * sh_info = 0 + * sh_addralign = 8 + * sh_entsize = MELF_MCPU_TARGET_SIZE + * + * It is never MELF_SHF_ALLOC and never carries an MCPU segment selector. + * + * mcpu-ld must reject ET_REL inputs whose target profiles are incompatible. + * For ABI version 1 the semantic profile fields must match exactly: + * + * mt_version + * mt_isa_revision + * mt_abi_revision + * mt_program_model + * mt_data_byte_order + * mt_data_word_order + * mt_integer_model + * mt_address_model + * mt_register_model + * mt_real_model + * mt_complex_model + * mt_max_align + * mt_flags + * mt_required_features + * + * mt_size is validated as the record size, not as a target property. + * + * mcpu-run must validate the final ET_EXEC target profile against the + * runtime before mapping/executing guest contents. It must reject an + * incompatible target rather than reinterpret or byte-convert DATA. + */ + + +/* + * ABI-v1 target-profile invariants. + * + * The current architecture requires: + * + * mt_version == MELF_MCPU_TARGET_VERSION_1 + * mt_isa_revision == MELF_MCPU_ISA_REVISION_1 + * mt_abi_revision == MELF_MCPU_ABI_REVISION_1 + * mt_integer_model == MELF_MCPU_INTEGER_MODEL_TWOS_COMPLEMENT + * mt_address_model == MELF_MCPU_ADDRESS_MODEL_PTR64_SEG4_OFF60 + * mt_register_model == MELF_MCPU_REGISTER_MODEL_REG24_512K + * mt_real_model == MELF_MCPU_REAL_MODEL_LIBMPU + * mt_complex_model == MELF_MCPU_COMPLEX_MODEL_LIBMPU + * mt_max_align == MELF_MCPU_ABI_MAX_ALIGN + * mt_flags == 0 + * + * mt_program_model, mt_data_byte_order and mt_data_word_order are selected + * by the configured target profile and must be supported by the toolchain/ + * runtime. mt_data_byte_order must equal EI_DATA. + */ + + +/* + * -------------------------------------------------------------------------- + * MCPU lifecycle metadata + * -------------------------------------------------------------------------- + * + * Lifecycle entry ADDRESSES are NOT duplicated here. + * + * Their single authoritative representation remains MElf*_Ehdr: + * + * e_entry + * e_init_entry + * e_life_entry + * e_fini_entry + * + * .mcpu.lifecycle records which lifecycle contract gives those header + * fields their meaning. This avoids two independently relocatable copies + * of _start/_life/_fini addresses. + */ + + +/* Lifecycle metadata record/layout version. */ +#define MELF_MCPU_LIFECYCLE_VERSION_NONE 0U +#define MELF_MCPU_LIFECYCLE_VERSION_1 1U +#define MELF_MCPU_LIFECYCLE_VERSION_CURRENT MELF_MCPU_LIFECYCLE_VERSION_1 + + +/* Lifecycle ABI identifier. */ +#define MELF_MCPU_LIFECYCLE_ABI_NONE 0U +#define MELF_MCPU_LIFECYCLE_ABI_1 1U +#define MELF_MCPU_LIFECYCLE_ABI_CURRENT MELF_MCPU_LIFECYCLE_ABI_1 + + +/* + * Lifecycle ABI semantic flags. + * + * These describe the contract, not the actual presence of .init/.fini. + * Actual presence is determined solely from e_init_entry/e_fini_entry. + */ +#define MELF_MCPU_LF_START_REQUIRED 0x0000000000000001ULL +#define MELF_MCPU_LF_LIFE_REQUIRED 0x0000000000000002ULL +#define MELF_MCPU_LF_INIT_OPTIONAL 0x0000000000000004ULL +#define MELF_MCPU_LF_FINI_OPTIONAL 0x0000000000000008ULL +#define MELF_MCPU_LF_ENTRY_IS_START 0x0000000000000010ULL +#define MELF_MCPU_LF_NOINIT_START_EQ_LIFE 0x0000000000000020ULL +#define MELF_MCPU_LF_LIFE_END_RESUME 0x0000000000000040ULL +#define MELF_MCPU_LF_EXIT_ENTERS_FINI 0x0000000000000080ULL +#define MELF_MCPU_LF_STARTUP_PERSISTENT 0x0000000000000100ULL + +#define MELF_MCPU_LIFECYCLE_ABI1_FLAGS \ + (MELF_MCPU_LF_START_REQUIRED | \ + MELF_MCPU_LF_LIFE_REQUIRED | \ + MELF_MCPU_LF_INIT_OPTIONAL | \ + MELF_MCPU_LF_FINI_OPTIONAL | \ + MELF_MCPU_LF_ENTRY_IS_START | \ + MELF_MCPU_LF_NOINIT_START_EQ_LIFE | \ + MELF_MCPU_LF_LIFE_END_RESUME | \ + MELF_MCPU_LF_EXIT_ENTERS_FINI | \ + MELF_MCPU_LF_STARTUP_PERSISTENT) + + +/* + * Fixed class-independent lifecycle metadata record. + * + * Serialized size: 32 bytes. + * + * The concrete ABI-v1 startup-area layout is defined below by the + * loader/startup contract. The STARTUP_PERSISTENT flag fixes the rule + * that startup data constructed before _start remains valid for the + * lifetime of the executable instance. + */ +typedef struct +{ + __mpu_uint32_t ml_version; + __mpu_uint32_t ml_size; + + __mpu_uint32_t ml_abi; + __mpu_uint32_t ml_reserved0; + + __mpu_uint64_t ml_flags; + __mpu_uint64_t ml_reserved1; + +} MElf_McpuLifecycle; + + +#define MELF_MCPU_LIFECYCLE_SIZE 32U + +#define MELF_MCPU_LIFECYCLE_ML_VERSION_OFF 0U +#define MELF_MCPU_LIFECYCLE_ML_SIZE_OFF 4U +#define MELF_MCPU_LIFECYCLE_ML_ABI_OFF 8U +#define MELF_MCPU_LIFECYCLE_ML_RESERVED0_OFF 12U +#define MELF_MCPU_LIFECYCLE_ML_FLAGS_OFF 16U +#define MELF_MCPU_LIFECYCLE_ML_RESERVED1_OFF 24U + + +/* + * .mcpu.lifecycle section rules. + * + * ET_REL: + * .mcpu.lifecycle is not emitted. Relocatable objects carry lifecycle + * names through the ordinary symbol table (_start/_life/_fini), while + * final lifecycle composition is a linker responsibility. + * + * ET_EXEC: + * exactly one .mcpu.lifecycle section is mandatory. + * + * The section has: + * + * sh_type = MELF_SHT_MCPU_LIFECYCLE + * sh_flags = 0 + * sh_addr = 0 + * sh_size = MELF_MCPU_LIFECYCLE_SIZE + * sh_link = 0 + * sh_info = 0 + * sh_addralign = 8 + * sh_entsize = MELF_MCPU_LIFECYCLE_SIZE + * + * It is metadata only: not MELF_SHF_ALLOC and no MCPU segment selector. + * + * ABI-v1 ET_EXEC requires: + * + * ml_version == MELF_MCPU_LIFECYCLE_VERSION_1 + * ml_size == MELF_MCPU_LIFECYCLE_SIZE + * ml_abi == MELF_MCPU_LIFECYCLE_ABI_1 + * ml_reserved0 == 0 + * ml_flags == MELF_MCPU_LIFECYCLE_ABI1_FLAGS + * ml_reserved1 == 0 + */ + + +/* + * Lifecycle/header consistency checks for ET_EXEC. + * + * Every valid executable: + * + * e_entry != 0 + * e_life_entry != 0 + * e_entry == st_value(_start) + * e_life_entry == st_value(_life) + * + * With .init: + * + * e_init_entry != 0 + * e_init_entry == e_entry + * _start belongs to .init + * _life belongs to .text + * + * Without .init: + * + * e_init_entry == 0 + * _start and _life both belong to .text + * st_value(_start) == st_value(_life) + * e_entry == e_life_entry + * + * With .fini: + * + * e_fini_entry == st_value(_fini) + * _fini belongs to .fini + * + * Without .fini: + * + * e_fini_entry == 0 + * + * _start and _life are mandatory GLOBAL/FUNC symbols. _fini, when + * present, is also GLOBAL/FUNC. None of the lifecycle symbols may be + * WEAK. + */ + + +/* + * -------------------------------------------------------------------------- + * MELF program-header ABI and MCPU segment mapping + * -------------------------------------------------------------------------- + * + * Program headers describe the final ET_EXEC memory image. They do not + * describe host virtual memory mappings. Guest architectural addresses + * are MCPU PTR64 values; host addresses are never stored in MELF. + * + * ET_REL has no Program Header Table: + * + * e_phoff = 0 + * e_phentsize = 0 + * e_phnum = 0 + * + * ET_EXEC has a Program Header Table whose entries are described below. + */ + + +/* + * Program-header types. + * + * MELF_PT_NULL and MELF_PT_LOAD retain the conventional ELF meanings. + * Runtime-only MCPU STACK and HEAP descriptors use processor-specific + * program-header types. + */ +#define MELF_PT_NULL 0U +#define MELF_PT_LOAD 1U + +#define MELF_PT_LOPROC 0x70000000U +#define MELF_PT_MCPU_STACK 0x70000000U +#define MELF_PT_MCPU_HEAP 0x70000001U +#define MELF_PT_HIPROC 0x7fffffffU + + +/* + * Program-header permission flags. + * + * These are GUEST CPU permissions. Host/runtime access is an independent + * implementation matter. In particular CONTROL and INPUT are guest + * read-only even though the host runtime may update their backing storage + * at defined synchronization points. + */ +#define MELF_PF_X 0x00000001U +#define MELF_PF_W 0x00000002U +#define MELF_PF_R 0x00000004U + + +/* Canonical ABI-v1 guest permissions. */ +#define MELF_PFLAGS_PROGRAM (MELF_PF_R | MELF_PF_X) +#define MELF_PFLAGS_DATA (MELF_PF_R | MELF_PF_W) +#define MELF_PFLAGS_CONTROL (MELF_PF_R) +#define MELF_PFLAGS_INPUT (MELF_PF_R) +#define MELF_PFLAGS_OUTPUT (MELF_PF_R | MELF_PF_W) +#define MELF_PFLAGS_TRACE (MELF_PF_R | MELF_PF_W) +#define MELF_PFLAGS_STACK (MELF_PF_R | MELF_PF_W) +#define MELF_PFLAGS_HEAP (MELF_PF_R | MELF_PF_W) + + +/* + * MCPU PTR64 helpers used by MELF64 executable metadata. + * + * Current MCPU ABI: + * + * bits 63..60 : SEG + * bits 59..0 : byte OFFSET + * + * PTR64 value zero is architectural NULL. + * + * MELF32 cannot represent the current PTR64 architecture and remains + * reserved for a possible future ABI with separately specified semantics. + */ +#define MELF_MCPU_PTR64_SEG_SHIFT 60U +#define MELF_MCPU_PTR64_SEG_MASK 0xf000000000000000ULL +#define MELF_MCPU_PTR64_OFFSET_MASK 0x0fffffffffffffffULL + +#define MELF_MCPU_PTR64(seg, off) \ + ((MElf64_Addr)((((MElf64_Xword)(seg) & 0x0fULL) << \ + MELF_MCPU_PTR64_SEG_SHIFT) | \ + ((MElf64_Xword)(off) & MELF_MCPU_PTR64_OFFSET_MASK))) + +#define MELF_MCPU_PTR64_SEG(addr) \ + ((unsigned)((((MElf64_Xword)(addr)) & MELF_MCPU_PTR64_SEG_MASK) >> \ + MELF_MCPU_PTR64_SEG_SHIFT)) + +#define MELF_MCPU_PTR64_OFFSET(addr) \ + ((MElf64_Xword)(((MElf64_Xword)(addr)) & \ + MELF_MCPU_PTR64_OFFSET_MASK)) + + +/* + * Canonical architectural segment-base addresses for MELF64 ET_EXEC. + * + * Every program header describing a complete MCPU architectural space + * starts at byte offset zero of that space. + */ +#define MELF_MCPU_VADDR_DATA \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_DATA, 0) + +#define MELF_MCPU_VADDR_STACK \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_STACK, 0) + +#define MELF_MCPU_VADDR_PROGRAM \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_PROGRAM, 0) + +#define MELF_MCPU_VADDR_CONTROL \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_CONTROL, 0) + +#define MELF_MCPU_VADDR_INPUT \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_INPUT, 0) + +#define MELF_MCPU_VADDR_OUTPUT \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_OUTPUT, 0) + +#define MELF_MCPU_VADDR_TRACE \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_TRACE, 0) + +#define MELF_MCPU_VADDR_HEAP \ + MELF_MCPU_PTR64(MELF_MCPU_SEG_HEAP, 0) + + +/* + * MELF32 program header. + * + * p_maxsz is a MELF extension. Its general meaning is the maximum + * architectural size allowed for the described MCPU space. + * + * Serialized ABI size: 36 bytes. + * + * MELF32 is reserved and is not accepted by MCPU ABI version 1. + */ +typedef struct +{ + MElf32_Word p_type; + MElf32_Off p_offset; + MElf32_Addr p_vaddr; + MElf32_Addr p_paddr; + MElf32_Word p_filesz; + MElf32_Word p_memsz; + MElf32_Word p_maxsz; + MElf32_Word p_flags; + MElf32_Word p_align; + +} MElf32_Phdr; + + +/* + * MELF64 program header. + * + * p_maxsz is a MELF extension beyond the conventional ELF64 Phdr. + * + * Serialized ABI size: 64 bytes. + */ +typedef struct +{ + MElf64_Word p_type; + MElf64_Word p_flags; + MElf64_Off p_offset; + MElf64_Addr p_vaddr; + MElf64_Addr p_paddr; + MElf64_Xword p_filesz; + MElf64_Xword p_memsz; + MElf64_Xword p_maxsz; + MElf64_Xword p_align; + +} MElf64_Phdr; + + +#define MELF32_PHDR_SIZE 36U +#define MELF64_PHDR_SIZE 64U + + +/* MELF32 program-header serialized offsets. */ +#define MELF32_PHDR_P_TYPE_OFF 0U +#define MELF32_PHDR_P_OFFSET_OFF 4U +#define MELF32_PHDR_P_VADDR_OFF 8U +#define MELF32_PHDR_P_PADDR_OFF 12U +#define MELF32_PHDR_P_FILESZ_OFF 16U +#define MELF32_PHDR_P_MEMSZ_OFF 20U +#define MELF32_PHDR_P_MAXSZ_OFF 24U +#define MELF32_PHDR_P_FLAGS_OFF 28U +#define MELF32_PHDR_P_ALIGN_OFF 32U + + +/* MELF64 program-header serialized offsets. */ +#define MELF64_PHDR_P_TYPE_OFF 0U +#define MELF64_PHDR_P_FLAGS_OFF 4U +#define MELF64_PHDR_P_OFFSET_OFF 8U +#define MELF64_PHDR_P_VADDR_OFF 16U +#define MELF64_PHDR_P_PADDR_OFF 24U +#define MELF64_PHDR_P_FILESZ_OFF 32U +#define MELF64_PHDR_P_MEMSZ_OFF 40U +#define MELF64_PHDR_P_MAXSZ_OFF 48U +#define MELF64_PHDR_P_ALIGN_OFF 56U + + +/* + * p_paddr. + * + * MCPU has no separate physical-address namespace in MELF ABI version 1. + * Therefore every non-NULL ABI-v1 program header has: + * + * p_paddr = 0 + * + * Writers emit zero and readers reject non-zero p_paddr unless a future + * ABI explicitly assigns it a meaning. + */ + + +/* + * p_align. + * + * p_align is a guest/file layout requirement, never a host mmap page size. + * + * Values 0 and 1 mean no special alignment. Any larger value must be a + * power of two. + * + * For MELF_PT_LOAD: + * + * p_offset % p_align == MELF_MCPU_PTR64_OFFSET(p_vaddr) % p_align + * + * Since ABI-v1 architectural load segments begin at segment offset zero, + * a nontrivial p_align therefore also aligns p_offset. + * + * p_align must be at least the maximum sh_addralign requirement of every + * allocated section contained in the segment. + * + * Host page rounding is outside the MELF ABI. + */ + + +/* + * Image-backed architectural spaces. + * + * A final ET_EXEC contains at most one MELF_PT_LOAD for each of: + * + * PROGRAM + * DATA + * CONTROL + * INPUT + * OUTPUT + * TRACE + * + * PROGRAM is mandatory. + * + * DATA is emitted by mcpu-ld when the final DATA architectural space is + * present. When present, it includes the single linker-owned 16-byte + * DATA NULL guard at offsets 0..15. + * + * CONTROL, INPUT, OUTPUT and TRACE load segments are emitted when the + * corresponding architectural space exists in the linked executable. + * + * STACK and HEAP never use MELF_PT_LOAD; they use the runtime-only + * descriptors MELF_PT_MCPU_STACK and MELF_PT_MCPU_HEAP below. + * + * Reserved selectors 0x8..0xf cannot appear in an ABI-v1 program header. + */ + + +/* + * MELF_PT_LOAD common rules. + * + * p_vaddr: + * full PTR64(segment, 0), identifying one complete MCPU architectural + * space. + * + * p_offset: + * file offset of the initial-image byte corresponding to architectural + * segment offset zero. If p_filesz == 0, no file bytes are consumed + * and p_offset has no load effect, though writers still keep it valid + * and aligned. + * + * p_filesz: + * number of bytes stored in MELF for the initial segment image. + * + * p_memsz: + * exact guest-visible architectural segment size. + * + * p_maxsz: + * equals p_memsz for every MELF_PT_LOAD in ABI version 1. + * + * Required relation: + * + * p_filesz <= p_memsz + * p_memsz == p_maxsz + * + * Load semantics: + * + * architectural bytes [0, p_filesz) are initialized from the file; + * architectural bytes [p_filesz, p_memsz) are initialized to zero. + * + * The stored file image includes any linker-created padding that lies + * before the last file-backed byte. Thus a PROGBITS section contained in + * a load segment obeys: + * + * sh_offset - p_offset == + * MELF_MCPU_PTR64_OFFSET(sh_addr) - + * MELF_MCPU_PTR64_OFFSET(p_vaddr) + * + * NOBITS sections consume architectural memory but no file payload. + * + * The loader creates independent anonymous host backing and copies/reads + * the initial image into it. MELF does not require or authorize direct + * file-backed guest mappings. + */ + + +/* + * PROGRAM load segment. + * + * Exactly one PROGRAM MELF_PT_LOAD exists in every ET_EXEC: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_PROGRAM + * p_paddr = 0 + * p_flags = MELF_PFLAGS_PROGRAM + * + * .init, .text and .fini remain separate section-header entities but are + * laid out in this one PROGRAM architectural space. Their lifecycle + * entry addresses are carried by MElf*_Ehdr, not by the Phdr. + * + * p_memsz is the exact final program.size used by CPU bounds checks. + */ + + +/* + * DATA load segment. + * + * When present: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_DATA + * p_paddr = 0 + * p_flags = MELF_PFLAGS_DATA + * + * DATA offsets 0..15 are the one linker-owned NULL guard. No ordinary + * linked object may occupy that range. + * + * .data and .bss map to this same architectural DATA space. + */ + + +/* + * CONTROL load segment. + * + * When present: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_CONTROL + * p_paddr = 0 + * p_flags = MELF_PFLAGS_CONTROL + * + * CONTROL is guest read-only. Its initial contents are linked from + * .control contributions. Host/runtime write access is not represented + * by MELF_PF_W. + */ + + +/* + * INPUT load segment. + * + * When present: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_INPUT + * p_paddr = 0 + * p_flags = MELF_PFLAGS_INPUT + * + * INPUT is guest read-only. The runtime may provide/update input values + * according to the execution/model policy defined outside guest write + * permissions. + */ + + +/* + * OUTPUT load segment. + * + * When present: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_OUTPUT + * p_paddr = 0 + * p_flags = MELF_PFLAGS_OUTPUT + */ + + +/* + * TRACE load segment. + * + * When present: + * + * p_type = MELF_PT_LOAD + * p_vaddr = MELF_MCPU_VADDR_TRACE + * p_paddr = 0 + * p_flags = MELF_PFLAGS_TRACE + * + * TRACE represents the current telemetry frame, not an accumulating log. + */ + + +/* + * Runtime-created STACK descriptor. + * + * STACK has no MELF file payload. + * + * Exactly one MELF_PT_MCPU_STACK is mandatory in every ET_EXEC: + * + * p_type = MELF_PT_MCPU_STACK + * p_offset = 0 + * p_vaddr = MELF_MCPU_VADDR_STACK + * p_paddr = 0 + * p_filesz = 0 + * p_memsz = initial allocated STACK size + * p_maxsz = p_memsz + * p_flags = MELF_PFLAGS_STACK + * p_align = at least 16 + * + * ABI version 1 describes a fixed allocated STACK size; p_maxsz therefore + * equals p_memsz. + * + * Before startup data is constructed an empty allocated stack has: + * + * SP = FP = stack_size + * + * The loader then constructs the persistent ABI-v1 startup area at the + * high end of STACK and sets the architectural initial SP/FP according to + * the stage-8 startup contract below. + * + * STACK size is part of the executable ABI. mcpu-run has no stack-size + * override for MELF and must not replace p_memsz with a command-line or + * host policy value. + */ + + +/* + * Runtime-created HEAP descriptor. + * + * HEAP has no MELF file payload and is always created zero-filled. + * + * When MELF_PT_MCPU_HEAP is present: + * + * p_type = MELF_PT_MCPU_HEAP + * p_offset = 0 + * p_vaddr = MELF_MCPU_VADDR_HEAP + * p_paddr = 0 + * p_filesz = 0 + * p_memsz = initial zero-filled HEAP backing/logical extent + * p_maxsz = immutable maximum HEAP size permitted by the executable profile + * p_flags = MELF_PFLAGS_HEAP + * p_align = at least 16 + * + * Required relation: + * + * p_memsz <= p_maxsz + * + * HEAP offsets 0..15 remain the architectural heap guard. Therefore a + * non-empty ABI-v1 HEAP descriptor must reserve at least those 16 bytes. + * + * The linker-defined symbol _end is HEAP:16. At process start _end is + * also the initial program break and the minimum value to which the break + * may ever be reduced. Guest HEAP dereference is bounded by the current + * program break, not by p_memsz or by host mapping capacity. + * + * p_maxsz records an immutable executable heap limit. SYS_brk may move + * the program break in the inclusive range [HEAP:16, p_maxsz]. LibMCPU + * may grow or relocate host backing when required, but p_maxsz is never + * enlarged and guest PTR64 HEAP values remain segment+offset addresses. + * Shrinking the break keeps the backing allocation and zeroes the released + * range before it can later become accessible again. + */ + + +/* + * Program Header Table consistency with Section Headers. + * + * Every MELF_SHF_ALLOC section in ET_EXEC must belong to exactly one + * image-backed MELF_PT_LOAD whose segment selector matches the selector + * encoded in sh_flags and in sh_addr. + * + * For every such section: + * + * MELF_MCPU_PTR64_SEG(sh_addr) == + * MELF_MCPU_PTR64_SEG(p_vaddr) + * + * and the complete architectural range: + * + * [OFFSET(sh_addr), OFFSET(sh_addr) + sh_size) + * + * must lie within: + * + * [0, p_memsz) + * + * Non-ALLOC metadata sections (.symtab, .strtab, .shstrtab, + * .mcpu.target, .mcpu.lifecycle, .rela.*) do not require and must not + * acquire guest PT_LOAD coverage solely because they exist in the file. + */ + + +/* + * Canonical ABI-v1 program-header order. + * + * mcpu-ld emits entries in this deterministic order when present: + * + * PROGRAM + * DATA + * CONTROL + * INPUT + * OUTPUT + * TRACE + * STACK + * HEAP + * + * Readers must identify entries by p_type/p_vaddr and must not depend on + * table order. Duplicate descriptors for one architectural MCPU space + * are invalid in ABI version 1. + */ + + +/* + * -------------------------------------------------------------------------- + * Static-linker, loader/startup and runtime lifecycle contract + * -------------------------------------------------------------------------- + * + * This section fixes specification stage 8. + * + * Core rule: + * + * mcpu-ld constructs the complete executable image. + * mcpu-run validates and instantiates it. + * mcpu-run never repairs or relinks it. + */ + + +/* + * -------------------------------------------------------------------------- + * Static linker contract: mcpu-ld + * -------------------------------------------------------------------------- + * + * mcpu-ld is responsible for all link-time semantic work before ET_EXEC is + * handed to the loader. + * + * For ABI-v1 output mcpu-ld must: + * + * 1. accept only compatible MCPU ET_REL input profiles; + * + * 2. resolve LOCAL/GLOBAL/WEAK symbols according to the symbol ABI; + * + * 3. reject unresolved strong GLOBAL symbols; + * + * 4. evaluate and apply every required RELA relocation, including all + * segment restrictions, signed-addend checks and range checks; + * + * 5. construct final PROGRAM/DATA/CONTROL/INPUT/OUTPUT/TRACE layouts; + * + * 6. insert exactly one linker-owned 16-byte DATA NULL guard at + * DATA offsets 0..15 when final DATA exists; + * + * 7. preserve the required alignment of every input contribution; + * + * 8. compute final sh_addr/st_value architectural addresses; + * + * 9. validate the lifecycle symbols and write: + * + * e_entry + * e_init_entry + * e_life_entry + * e_fini_entry + * + * 10. emit the final Program Header Table, including exactly one + * mandatory MELF_PT_MCPU_STACK descriptor; + * + * 11. emit the final .mcpu.target and .mcpu.lifecycle metadata; + * + * 12. set all executable segment sizes and limits, including STACK size + * and optional HEAP initial/maximum sizes. + * + * ABI-v1 ET_EXEC contains no unresolved/runtime relocation requirement. + * SHT_RELA sections are ET_REL link input; mcpu-ld does not emit SHT_RELA + * sections into the final ABI-v1 ET_EXEC. + * + * A symbol table may be retained in ET_EXEC for diagnostics/debugging, but + * the runtime loader must not require _start/_life/_fini symbol names. + * Numeric Ehdr lifecycle entries are authoritative at runtime. + */ + + +/* + * STACK size is executable ABI. + * + * The legacy mcpu-run --stack-size behavior is not part of the MELF ABI and + * must be removed from the MELF runtime. mcpu-run does not accept an + * alternate STACK size for a MELF executable. + * + * The final STACK size is p_memsz of MELF_PT_MCPU_STACK and can only be + * changed by producing/relinking a different executable. + */ + + +/* + * -------------------------------------------------------------------------- + * ABI-v1 startup area + * -------------------------------------------------------------------------- + * + * The loader constructs a persistent startup area in STACK before the + * first instruction of _start executes. + * + * All numeric fields and PTR64 values in this guest structure use the + * target architectural data-object representation. It is NOT a MELF + * metadata record and is NOT serialized with host C structure layout. + */ + + +/* Startup-area structure/layout version. */ +#define MCPU_STARTUP_VERSION_NONE 0U +#define MCPU_STARTUP_VERSION_1 1U +#define MCPU_STARTUP_VERSION_CURRENT MCPU_STARTUP_VERSION_1 + + +/* Startup block layout constants. */ +#define MCPU_STARTUP_ALIGN 16U +#define MCPU_STARTUP_SIZE 32U +#define MCPU_STARTUP_ARGV_SIZE 8U + + +/* + * Guest PTR64 scalar used by the startup ABI. + * + * Current MCPU ABI is intrinsically PTR64 even though a future MELF32 + * container class remains reserved. + */ +typedef __mpu_uint64_t McpuPtr64; + + +/* + * Persistent startup header. + * + * The type name intentionally does not carry "V1": the structure itself + * contains a version field. + * + * Guest-memory ABI layout: + * + * +0x00 uint32 version + * +0x04 uint32 size + * +0x08 uint64 argc + * +0x10 PTR64 argv + * +0x18 uint64 reserved + * + * version = MCPU_STARTUP_VERSION_1 + * size = MCPU_STARTUP_SIZE + * argc = number of argument strings + * argv = PTR64(STACK, argv_array_offset) + * reserved = 0 + */ +typedef struct McpuStartup +{ + __mpu_uint32_t version; + __mpu_uint32_t size; + + __mpu_uint64_t argc; + McpuPtr64 argv; + + __mpu_uint64_t reserved; + +} McpuStartup; + + +#define MCPU_STARTUP_VERSION_OFF 0U +#define MCPU_STARTUP_SIZE_OFF 4U +#define MCPU_STARTUP_ARGC_OFF 8U +#define MCPU_STARTUP_ARGV_OFF 16U +#define MCPU_STARTUP_RESERVED_OFF 24U + + +/* + * argv[] representation. + * + * argv points to an array of argc + 1 guest PTR64 values: + * + * argv[0] -> first argument byte string + * ... + * argv[argc - 1] -> last argument byte string + * argv[argc] = 0 (NULL) + * + * Every non-NULL argv[i] is PTR64(STACK, string_offset). + * + * Argument strings are NUL-terminated byte strings copied into the + * persistent startup area. The byte-string interpretation supplied by + * the invoking runtime/toolchain is outside the MELF container ABI; the + * loader does not perform target-endian conversion on string bytes. + * + * argc == 0 is valid and still creates the terminating argv[0] == NULL. + */ + + +/* + * Canonical ABI-v1 startup-area layout. + * + * Let: + * + * stack_size = MELF_PT_MCPU_STACK.p_memsz + * + * The loader computes: + * + * header_bytes = MCPU_STARTUP_SIZE + * argv_bytes = (argc + 1) * MCPU_STARTUP_ARGV_SIZE + * string_bytes = sum(strlen(argument[i]) + 1) + * + * with checked arithmetic. + * + * The variable data are arranged from low to high addresses as: + * + * startup_base: + * McpuStartup + * argv[argc + 1] + * argument string bytes + * zero padding, if any + * stack_size: + * + * startup_base is chosen so that: + * + * startup_base % MCPU_STARTUP_ALIGN == 0 + * + * and the whole startup region: + * + * [startup_base, stack_size) + * + * fits in the allocated STACK. + * + * The argv array is naturally 8-byte aligned because McpuStartup is + * 32 bytes and startup_base is 16-byte aligned. + * + * The loader writes: + * + * McpuStartup.version = MCPU_STARTUP_VERSION_1 + * McpuStartup.size = MCPU_STARTUP_SIZE + * McpuStartup.argc = argc + * McpuStartup.argv = PTR64(STACK, + * startup_base + MCPU_STARTUP_SIZE) + * McpuStartup.reserved = 0 + * + * All unused padding bytes in the startup region are zero. + * + * If the supplied argument set does not fit while preserving the required + * alignment, program startup fails. mcpu-run must not enlarge STACK to + * make the invocation fit. + */ + + +/* + * Initial architectural state supplied by the loader. + * + * After STACK allocation and startup-area construction: + * + * PC = e_entry + * SP = startup_base + * FP = stack_size + * + * No artificial CALL frame or return PC is created for _start. + * + * Other CPU reset state is defined by the MCPU execution/reset contract; + * argc/argv are not passed through hidden host pointers or host registers. + * + * The startup region remains allocated and valid for the entire executable + * lifetime. Normal ABI stack frames grow downward below startup_base. + * The runtime never reclaims or moves the startup region. + */ + + +/* + * Initial _life argument source. + * + * _life is responsible for constructing the ordinary MCPU CALL-ABI + * argument block for main(argc, argv). main() is never entered directly + * by the loader. + * + * At the initial top-level lifecycle stack state, SP points to + * McpuStartup. _life obtains argc and argv from that structure, converts/ + * lays out arguments according to the language CALL ABI if required, and + * CALLs main. + */ + + +/* + * Top-level lifecycle stack discipline. + * + * Runtime lifecycle transitions do not silently repair SP or FP. + * + * If .init exists, _start may use ordinary stack frames during INIT, but + * before its normal terminating END it must restore the top-level state: + * + * SP = startup_base + * FP = stack_size + * + * The runtime then changes PC to e_life_entry and leaves the remaining + * architectural context live. + * + * A normal _life trampoline similarly unwinds its main() call and argument + * block before executing EXIT, so graceful FINI entry sees the same + * top-level stack state. + * + * _fini may use ordinary stack frames but must unwind them before its + * normal terminating END. + * + * This rule keeps McpuStartup reachable at SP on each initial lifecycle + * entry without introducing a hidden startup register. + */ + + +/* + * -------------------------------------------------------------------------- + * Assembler lifecycle convenience syntax + * -------------------------------------------------------------------------- + * + * When .init is absent, mcpu-as accepts: + * + * .text + * _start_life: + * ... + * + * as an assembler-only pseudo-label exactly equivalent to: + * + * .text + * _start: + * _life: + * ... + * + * mcpu-as emits the two normative MELF symbols: + * + * _start STB_GLOBAL / STT_FUNC + * _life STB_GLOBAL / STT_FUNC + * + * at the same section-relative byte offset. + * + * mcpu-as does not need to emit a distinct "_start_life" MELF symbol. + * + * It is an assembler error to use _start_life: if _start or _life has + * already been defined in the same translation unit. + * + * A final executable containing .init cannot use the _start_life form: + * with .init, _start must belong to .init and _life to .text. + */ + + +/* + * -------------------------------------------------------------------------- + * Loader structural-validation contract + * -------------------------------------------------------------------------- + * + * mcpu-run validates a MELF ET_EXEC before executing guest code. + * + * Validation must use checked integer arithmetic for every file offset, + * count, size, alignment and end-position calculation. A wraparound is a + * load error. + * + * The loader performs at least the following validation classes before + * guest execution: + * + * A. MELF identification/header + * - magic "_ELF"; + * - MELF_CLASS_64 for ABI v1; + * - supported EI_DATA; + * - EI_VERSION/e_version == MELF_EV_CURRENT; + * - OSABI == MELF_OSABI_MCPU; + * - ABI version == MELF_ABIVERSION_CURRENT; + * - e_type == MELF_ET_EXEC; + * - e_machine == MELF_EM_MCPU; + * - e_flags == MELF_EF_MCPU_NONE; + * - e_ehsize == MELF64_EHDR_SIZE; + * - e_phentsize == MELF64_PHDR_SIZE; + * - valid section/program-header table file ranges. + * + * B. target/lifecycle metadata + * - exactly one MELF_SHT_MCPU_TARGET; + * - exactly one MELF_SHT_MCPU_LIFECYCLE; + * - valid record sizes/versions/reserved fields; + * - target profile supported by this runtime; + * - mt_data_byte_order == EI_DATA; + * - lifecycle ABI/flags exactly supported by ABI v1. + * + * C. Program Header Table + * - exactly one PROGRAM MELF_PT_LOAD; + * - exactly one MELF_PT_MCPU_STACK; + * - at most one DATA/CONTROL/INPUT/OUTPUT/TRACE load descriptor; + * - at most one MELF_PT_MCPU_HEAP; + * - no duplicate architectural segment descriptors; + * - no reserved selector 0x8..0xf; + * - canonical guest permission flags; + * - p_paddr == 0; + * - valid p_align; + * - p_filesz <= p_memsz; + * - PT_LOAD p_maxsz == p_memsz; + * - STACK p_filesz == 0 and p_maxsz == p_memsz; + * - HEAP p_filesz == 0 and p_memsz <= p_maxsz; + * - every architectural size/offset fits PTR64 OFF60. + * + * D. section-to-segment consistency + * - every SHF_ALLOC section belongs to exactly one matching PT_LOAD; + * - sh_addr selector agrees with sh_flags and p_vaddr; + * - section ranges lie inside segment p_memsz; + * - PROGBITS file ranges lie inside the corresponding load image; + * - metadata sections are non-ALLOC. + * + * E. lifecycle entries + * - e_entry and e_life_entry are nonzero PROGRAM PTR64 values; + * - optional e_init_entry/e_fini_entry are zero or PROGRAM PTR64; + * - every nonzero lifecycle entry offset is < PROGRAM p_memsz; + * - with INIT: e_init_entry == e_entry; + * - without INIT: e_init_entry == 0 and e_entry == e_life_entry; + * - lifecycle/header metadata are mutually consistent. + * + * Runtime loading does not require a symbol table. When a symbol table is + * retained, additional diagnostic consistency checks are allowed, but + * stripping ordinary .symtab/.strtab must not change executable behavior. + */ + + +/* + * DATA NULL-guard validation. + * + * If a DATA segment exists, its architectural size must be at least + * 16 bytes and final loaded DATA bytes 0..15 must all be zero. + * + * The loader does not insert or repair this guard: mcpu-ld owns it. + */ + + +/* + * -------------------------------------------------------------------------- + * Runtime image-instantiation contract + * -------------------------------------------------------------------------- + * + * Program Headers, not Section Headers, are the authoritative runtime + * description of architectural memory spaces. + * + * The loader uses Section Headers for required non-ALLOC metadata and + * optional diagnostics, but it does not concatenate sections to construct + * PROGRAM/DATA/etc. at runtime. + * + * For each image-backed PT_LOAD the runtime: + * + * 1. creates independent anonymous backing of at least p_memsz bytes; + * 2. initializes all architectural bytes [0, p_memsz) to zero; + * 3. copies p_filesz bytes verbatim from MELF file offset p_offset to + * architectural offset zero; + * 4. records exact architectural size = p_memsz; + * 5. enforces the guest permissions represented by p_flags. + * + * File payload bytes are copied verbatim: + * + * - PROGRAM already contains canonical MSB-first machine fields; + * - DATA-like payload already contains target data-object representation. + * + * The loader never byte-swaps an image payload to match the host. + * + * Host page size, rounded mapping capacity, chosen host base address and + * use of mprotect() are implementation details outside the guest ABI. + * Guest bounds checks always use exact architectural p_memsz. + * + * MELF does not require or authorize guest architectural state to depend + * on a file-backed mmap address. + */ + + +/* + * STACK instantiation. + * + * Exactly one MELF_PT_MCPU_STACK is mandatory. + * + * The runtime creates zero-filled writable STACK backing of exactly + * p_memsz guest-visible bytes, then writes the persistent startup area at + * the high end as specified above. + * + * The runtime does not change STACK size according to command-line policy. + */ + + +/* + * HEAP instantiation. + * + * MELF_PT_MCPU_HEAP is optional. + * + * If present, the runtime creates zero-filled writable HEAP backing with + * initial architectural size p_memsz and configured maximum p_maxsz. + * + * HEAP offsets 0..15 remain the heap guard. Future growth may increase + * architectural heap.size up to p_maxsz through a separately defined + * runtime/system-call ABI; such growth is not an ELF relocation or loader + * repair operation. + */ + + +/* + * Guest read-only spaces. + * + * PROGRAM, CONTROL and INPUT are guest write-protected according to their + * canonical p_flags. + * + * The host/runtime may update CONTROL/INPUT backing only as permitted by + * the execution/model runtime contract. Host write access does not imply + * MELF_PF_W and does not alter guest permissions. + */ + + +/* + * -------------------------------------------------------------------------- + * Runtime lifecycle state machine + * -------------------------------------------------------------------------- + * + * Initial entry: + * + * loader constructs all memory and McpuStartup; + * PC = e_entry; + * execution begins at _start. + * + * No .init: + * + * e_init_entry == 0 + * e_entry == e_life_entry + * _start and _life are the same PROGRAM address. + * + * Therefore the first instruction simultaneously begins START/LIFE. + * + * With .init: + * + * e_entry == e_init_entry -> _start in .init. + * + * Normal INIT completion is END. After CPU_STOP_END the runtime begins + * LIFE by setting: + * + * PC = e_life_entry + * + * and otherwise preserving the live architectural context. The ABI + * top-level stack-discipline rule above requires _start to have restored + * SP/FP before this END. + * + * LIFE: + * + * END is a cooperative boundary. Decoder/executor has already advanced + * PC to the instruction after END. Runtime may consume OUTPUT/TRACE, + * update INPUT/time/scheduling state, and later resumes the saved PC. + * + * Runtime MUST NOT jump back to _life on LIFE resume. + * + * EXIT: + * + * EXIT records the signed program status and requests graceful + * termination. + * + * If e_fini_entry == 0, execution becomes FINISHED(status). + * + * If e_fini_entry != 0, runtime enters FINI exactly once by setting: + * + * PC = e_fini_entry + * + * and preserving the exit status until FINI completes. + * + * FINI: + * + * normal FINI completion is END, after which the entity becomes + * FINISHED(saved_exit_status). + * + * HALT, architectural fault and external forced termination are not + * graceful EXIT and do not automatically enter _fini. + */ + + +/* + * -------------------------------------------------------------------------- + * Loader validates, never repairs + * -------------------------------------------------------------------------- + * + * For MELF ABI version 1 mcpu-run must not: + * + * - resolve symbols; + * - apply RELA relocations; + * - insert the DATA NULL guard; + * - rewrite sh_addr/st_value/p_vaddr; + * - reorder target data bytes for host endian; + * - translate one target profile into another; + * - invent missing _start/_life lifecycle entries; + * - infer lifecycle entries from section names; + * - resize STACK; + * - override PT_MCPU_STACK with --stack-size; + * - change HEAP limits from executable values; + * - reinterpret malformed executable metadata in order to continue. + * + * Any contradiction in required ABI-v1 metadata/layout is a load error + * before guest execution. + */ + + +/* + * -------------------------------------------------------------------------- + * MELF ABI version 2: explicit startup dynamic linking + * -------------------------------------------------------------------------- + * + * ABI-v2 dynamic linking is deliberately a startup-only facility. It does + * not define DT_NEEDED, SONAME/RPATH, PLT/GOT, lazy binding, dlopen(), + * dlclose() or dlsym(). mcpu-run receives every shared object explicitly via + * --ldpreload=a.so:b.so and completes all symbol resolution/RELA processing + * before the first _start instruction executes. + * + * ABI-v3 keeps the same image, symbol and RELA model and adds the non-ALLOC + * .dynamic metadata described above. ABI-v3 ET_DYN has an explicit SONAME; + * ABI-v3 ET_EXEC/ET_DYN may record NEEDED names. Dependency discovery and + * recursive loading are defined incrementally while ABI-v3 remains unfrozen. + * + * Historical ABI-v2 ET_EXEC/ET_DYN files remain accepted by mcpu-run. + * Beginning with LibMCPU 0.2.14, mcpu-ld --dynamic and mcpu-ld --shared + * produce ABI-v3 files. ABI-v3 ET_EXEC still owns STACK/HEAP and the ordinary + * executable lifecycle and retains relocations through preemptible + * GLOBAL/WEAK names in runtime .rela.* sections. + * + * ABI-v2 and ABI-v3 ET_DYN contain only library .text/.data/.bss plus + * non-ALLOC metadata. They have no .init/.fini, CONTROL/INPUT/OUTPUT/TRACE, + * PT_MCPU_STACK/PT_MCPU_HEAP or lifecycle entry points. ET_DYN DATA starts at + * relative offset zero and has no per-library NULL guard; the process owns + * exactly one DATA NULL guard. ABI-v3 ET_DYN additionally requires SONAME. + * + * Runtime symbols are separate from developer/debug symbols: + * + * .dynsym / .dynstr startup-link ABI and therefore retained; + * .symtab / .strtab optional developer/debug information. + * + * ABI-v2/ABI-v3 .dynsym uses MElf64_Sym. Entry zero is all-zero. All nonzero + * entries use DEFAULT visibility and GLOBAL/WEAK binding. Defined st_value + * is byte offset relative to the output section named by st_shndx. Undefined + * entries have SHN_UNDEF and st_value/st_size zero. + * + * ABI-v2/ABI-v3 runtime SHT_RELA sections use MElf64_Rela and link to .dynsym. + * sh_info names the allocated section containing the relocation field. + * r_offset is a byte offset relative to that output section. This makes a + * retained relocation independent of the final process base assigned to an + * ET_DYN module. The five existing MCPU relocation meanings and byte-order + * domains are unchanged. + * + * Symbol interposition namespace for: + * + * --ldpreload=a.so:b.so:c.so + * + * is main, a, b, c with later modules having higher priority among symbols of + * the same binding. A strong GLOBAL definition always wins over every WEAK + * definition; among GLOBALs the rightmost module wins, and when no GLOBAL + * exists the rightmost WEAK wins. Interposition applies even to a reference + * whose originating module also defines that DEFAULT GLOBAL/WEAK name. + * Lifecycle names _start/_life/_fini are executable-private and never + * preemptible. + * + * An unresolved strong runtime symbol is a load error before execution. + * An unresolved WEAK pointer relocation with addend zero resolves to NULL as + * in the static ABI; other unresolved WEAK relocation uses are errors. + */ + +/* + * -------------------------------------------------------------------------- + * Current target aliases + * -------------------------------------------------------------------------- + * + * CPU_MELF_CLASS is a TARGET configuration property, never a property of + * the host compiler/platform. ABI version 1 supports MELF_CLASS_64 only. + */ + +#ifndef CPU_MELF_CLASS +#define CPU_MELF_CLASS MELF_CLASS_64 +#endif + +#if CPU_MELF_CLASS == MELF_CLASS_64 + +typedef MElf64_Half MElfN_Half; +typedef MElf64_Word MElfN_Word; +typedef MElf64_Sword MElfN_Sword; +typedef MElf64_Xword MElfN_Xword; +typedef MElf64_Sxword MElfN_Sxword; +typedef MElf64_Addr MElfN_Addr; +typedef MElf64_Off MElfN_Off; + +typedef MElf64_Ehdr MElfN_Ehdr; +typedef MElf64_Shdr MElfN_Shdr; +typedef MElf64_Sym MElfN_Sym; +typedef MElf64_Rela MElfN_Rela; +typedef MElf64_Phdr MElfN_Phdr; + +#define MELFN_EHDR_SIZE MELF64_EHDR_SIZE +#define MELFN_SHDR_SIZE MELF64_SHDR_SIZE +#define MELFN_SYM_SIZE MELF64_SYM_SIZE +#define MELFN_RELA_SIZE MELF64_RELA_SIZE +#define MELFN_PHDR_SIZE MELF64_PHDR_SIZE + +#define MELFN_R_SYM(info) MELF64_R_SYM(info) +#define MELFN_R_TYPE(info) MELF64_R_TYPE(info) +#define MELFN_R_INFO(sym,type) MELF64_R_INFO((sym),(type)) + +#elif CPU_MELF_CLASS == MELF_CLASS_32 + +/* + * The type names exist for forward compatibility, but MCPU ABI version 1 + * does not accept MELF32 files. + */ +typedef MElf32_Half MElfN_Half; +typedef MElf32_Word MElfN_Word; +typedef MElf32_Sword MElfN_Sword; +typedef MElf32_Addr MElfN_Addr; +typedef MElf32_Off MElfN_Off; + +typedef MElf32_Ehdr MElfN_Ehdr; +typedef MElf32_Shdr MElfN_Shdr; +typedef MElf32_Sym MElfN_Sym; +typedef MElf32_Rela MElfN_Rela; +typedef MElf32_Phdr MElfN_Phdr; + +#define MELFN_EHDR_SIZE MELF32_EHDR_SIZE +#define MELFN_SHDR_SIZE MELF32_SHDR_SIZE +#define MELFN_SYM_SIZE MELF32_SYM_SIZE +#define MELFN_RELA_SIZE MELF32_RELA_SIZE +#define MELFN_PHDR_SIZE MELF32_PHDR_SIZE + +#define MELFN_R_SYM(info) MELF32_R_SYM(info) +#define MELFN_R_TYPE(info) MELF32_R_TYPE(info) +#define MELFN_R_INFO(sym,type) MELF32_R_INFO((sym),(type)) + +#else +#error "Unsupported CPU_MELF_CLASS" +#endif + + +#endif /* MCPU_ELF_H */ diff --git a/libmcpu.m4 b/libmcpu.m4 new file mode 100644 index 0000000..f81a1a6 --- /dev/null +++ b/libmcpu.m4 @@ -0,0 +1,231 @@ +dnl # +dnl # /usr/share/aclocal/libmcpu.m4 +dnl # +dnl # Configure paths for libmcpu +dnl # Andrey V.Kosteltsev + +dnl ============================================================ +dnl +dnl Synopsis: +dnl AC_CHECK_LIBMCPU([MIN-VERSION [, # minimum libmcpu version, e.g. 0.4.0 +dnl DEFAULT-WITH-LIBMCPU [, # default value for --with-libmcpu option +dnl ACTION-IF-FOUND [, # action to perform if libmcpu was found +dnl ACTION-IF-NOT-FOUND # action to perform if libmcpu was not found +dnl ]]]]) +dnl Examples: +dnl AC_CHECK_LIBMCPU(0.4.0) +dnl AC_CHECK_LIBMCPU(0.4.0,,,CFLAGS="$CFLAGS -DHAVE_LIBMCPU $LIBMCPU_CFLAGS") +dnl AC_CHECK_LIBMCPU(0.4.0,yes,CFLAGS="$CFLAGS -DHAVE_LIBMCPU") +dnl +dnl +dnl If you have to change prefix returned by mcpu-config script or change +dnl location of mcpu-config, you may set environment variable LIBMCPU_CONFIG, +dnl for example: +dnl +dnl # export LIBMCPU_CONFIG="/usr/bin/mcpu-config" +dnl +dnl ============================================================ +dnl +dnl ============================================================ +dnl auxilliary macros +dnl ============================================================ +AC_DEFUN([_AC_LIBMCPU_ERROR], [dnl +AC_MSG_RESULT([*FAILED*]) +cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 +$1 +EOT +exit 1 +]) + +AC_DEFUN([_AC_LIBMCPU_VERBOSE], [dnl +if test "x$verbose" = "xyes"; then + AC_MSG_RESULT([ $1]) +fi +]) + +dnl ============================================================ +dnl the user macro +dnl ============================================================ +AC_DEFUN([AC_CHECK_LIBMCPU], [dnl +dnl +dnl ============================================================ +dnl prerequisites +dnl ============================================================ +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_CPP])dnl +dnl +dnl ============================================================ +dnl set LIBMCPU_CONFIG variable +dnl ============================================================ +if test -z "$LIBMCPU_CONFIG"; then + LIBMCPU_CONFIG='mcpu-config' +fi +dnl +LIBMCPU_CPPFLAGS='' +LIBMCPU_CFLAGS='' +LIBMCPU_LDFLAGS='' +LIBMCPU_LIBS='' +AC_SUBST(LIBMCPU_CPPFLAGS) +AC_SUBST(LIBMCPU_CFLAGS) +AC_SUBST(LIBMCPU_LDFLAGS) +AC_SUBST(LIBMCPU_LIBS) +dnl +dnl ============================================================ +dnl command line options +dnl ============================================================ +AC_MSG_CHECKING(for libmcpu) +_AC_LIBMCPU_VERBOSE([]) +AC_ARG_WITH(libmcpu,dnl +[ --with-libmcpu[=yes|no] Build with libmcpu Library (default=]ifelse([$2],,yes,$2)[)],dnl +,dnl +with_libmcpu="ifelse([$2],,yes,$2)" +)dnl +_AC_LIBMCPU_VERBOSE([+ Command Line Options:]) +_AC_LIBMCPU_VERBOSE([ o --with-libmcpu=$with_libmcpu]) +dnl +dnl ============================================================ +dnl configuration +dnl ============================================================ +if test "x$with_libmcpu" != "xno"; then + libmcpu_version="" + libmcpu_location="" + libmcpu_type="" + libmcpu_cppflags="" + libmcpu_cflags="" + libmcpu_ldflags="" + libmcpu_libs="" + if test "x$with_libmcpu" = "xyes"; then + # via config script in $PATH + changequote(, )dnl + libmcpu_version=`($LIBMCPU_CONFIG --version) 2>/dev/null |\ + sed -e 's/^.*\([0-9]\.[0-9]*[ab.][0-9]*\).*$/\1/'` + changequote([, ])dnl + if test "x$libmcpu_version" != "x"; then + libmcpu_location=`$LIBMCPU_CONFIG --prefix` + libmcpu_type='installed' + libmcpu_cppflags=`$LIBMCPU_CONFIG --cppflags` + libmcpu_cflags=`$LIBMCPU_CONFIG --cflags` + libmcpu_ldflags=`$LIBMCPU_CONFIG --ldflags` + libmcpu_libs=`$LIBMCPU_CONFIG --libs` + fi + fi + dnl ======================================================== + dnl Check whether the found version is sufficiently new + dnl ======================================================== + _req_version="ifelse([$1],,1.0.0,$1)" + for _var in libmcpu_version _req_version; do + eval "_val=\"\$${_var}\"" + _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\1/'` + _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\2/'` + _rtype=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\3/'` + _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\4/'` + case $_rtype in + "a" ) _rtype=0 ;; + "b" ) _rtype=1 ;; + "." ) _rtype=2 ;; + esac + _hex=`echo dummy | awk '{ printf("%d%02d%1d%02d", major, minor, rtype, micro); }' \ + "major=$_major" "minor=$_minor" "rtype=$_rtype" "micro=$_micro"` + eval "${_var}_hex=\"\$_hex\"" + done + _AC_LIBMCPU_VERBOSE([+ Determined Versions:]) + _AC_LIBMCPU_VERBOSE([ o existing: $libmcpu_version -> 0x$libmcpu_version_hex]) + _AC_LIBMCPU_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) + _ok=0 + if test "x$libmcpu_version_hex" != "x"; then + if test "x$_req_version_hex" != "x"; then + if test $libmcpu_version_hex -ge $_req_version_hex; then + _ok=1 + fi + fi + fi + if test "x$_ok" = "x0"; then + _AC_LIBMCPU_ERROR([dnl + Found libmcpu version $libmcpu_version, but required at least version $_req_version. + Upgrade libmcpu under $libmcpu_location to $_req_version or higher first, please.]) + fi + dnl ======================================================== + dnl Perform libmcpu Sanity Compile Check + dnl ======================================================== + _ac_save_CPPFLAGS="$CPPFLAGS" + _ac_save_CFLAGS="$CFLAGS" + _ac_save_LDFLAGS="$LDFLAGS" + _ac_save_LIBS="$LIBS" + CPPFLAGS="$CPPFLAGS $libmcpu_cppflags" + CFLAGS="$CFLAGS $libmcpu_cflags" + LDFLAGS="$LDFLAGS $libmcpu_ldflags" + LIBS="$LIBS $libmcpu_libs" + _AC_LIBMCPU_VERBOSE([+ Test Build Environment:]) + _AC_LIBMCPU_VERBOSE([ o CPPFLAGS="$CPPFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o CFLAGS="$CFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o LDFLAGS="$LDFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o LIBS="$LIBS"]) + cross_compile=no + _AC_LIBMCPU_VERBOSE([+ Performing Sanity Checks:]) + _AC_LIBMCPU_VERBOSE([ o pre-processor test]) + AC_PREPROC_IFELSE([AC_LANG_SOURCE([[ +#include <libmcpu.h> + ]])], _ok=yes, _ok=no) + if test "x$_ok" != "xyes"; then + _AC_LIBMCPU_ERROR([dnl + Found libmcpu $libmcpu_version under $libmcpu_location, but + was unable to perform a sanity pre-processor check. This means + the libmcpu header libmcpu.h was not found. + We used the following build environment: + >> CPP="$CPP" + >> CPPFLAGS="$CPPFLAGS" + See config.log for possibly more details.]) + fi + _AC_LIBMCPU_VERBOSE([ o link check]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +#include <libmcpu.h> + +int main() +{ + __cpu_init(); + + __cpu_free_context(); + + return( 0 ); +} + ]])], _ok=yes, _ok=no) + if test "x$_ok" != "xyes"; then + _AC_LIBMCPU_ERROR([dnl + Found libmcpu $libmcpu_version under $libmcpu_location, but + was unable to perform a sanity linker check. This means + the libmcpu library libmcpu.a was not found. + We used the following build environment: + >> CC="$CC" + >> CFLAGS="$CFLAGS" + >> LDFLAGS="$LDFLAGS" + >> LIBS="$LIBS" + See config.log for possibly more details.]) + fi + CPPFLAGS="$_ac_save_CPPFLAGS" + CFLAGS="$_ac_save_CFLAGS" + LDFLAGS="$_ac_save_LDFLAGS" + LIBS="$_ac_save_LIBS" + + LIBMCPU_CPPFLAGS="$libmcpu_cppflags" + LIBMCPU_CFLAGS="$libmcpu_cflags" + LIBMCPU_LDFLAGS="$libmcpu_ldflags" + LIBMCPU_LIBS="$libmcpu_libs" + AC_SUBST(LIBMCPU_CPPFLAGS) + AC_SUBST(LIBMCPU_CFLAGS) + AC_SUBST(LIBMCPU_LDFLAGS) + AC_SUBST(LIBMCPU_LIBS) + _AC_LIBMCPU_VERBOSE([+ Final Results:]) + _AC_LIBMCPU_VERBOSE([ o LIBMCPU_CPPFLAGS="$LIBMCPU_CPPFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o LIBMCPU_CFLAGS="$LIBMCPU_CFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o LIBMCPU_LDFLAGS="$LIBMCPU_LDFLAGS"]) + _AC_LIBMCPU_VERBOSE([ o LIBMCPU_LIBS="$LIBMCPU_LIBS"]) +fi +if test "x$with_libmcpu" != "xno"; then + AC_MSG_RESULT([version $libmcpu_version, $libmcpu_type under $libmcpu_location]) + ifelse([$3], , :, [$3]) +else + AC_MSG_RESULT([no]) + ifelse([$4], , :, [$4]) +fi +]) + diff --git a/m4/README b/m4/README new file mode 100644 index 0000000..24d1d53 --- /dev/null +++ b/m4/README @@ -0,0 +1,5 @@ + +This directory is reserved for external/vendor Autoconf M4 macros. + +Project-owned LibMCPU macros belong in ../acsite.m4 so locally maintained +configure logic remains clearly separated from imported macro packages. diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..3fa0e04 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,16 @@ + +SUBDIRS = ru + +MAN1 = mcpu-as.1 \ + mcpu-ld.1 \ + mcpu-run.1 \ + mcpu-readelf.1 \ + mcpu-ar.1 \ + mcpu-nm.1 \ + mcpu-objdump.1 \ + mcpu-strip.1 \ + mcpu-objcopy.1 + +MAN7 = libmcpu.7 + +dist_man_MANS = $(MAN1) $(MAN7) diff --git a/man/libmcpu.7 b/man/libmcpu.7 new file mode 100644 index 0000000..bfaa765 --- /dev/null +++ b/man/libmcpu.7 @@ -0,0 +1,160 @@ +.TH LIBMCPU 7 "October 2026" "LibMCPU" "MCPU Architecture and Toolchain" + +.SH NAME +libmcpu \- MCPU virtual CPU architecture, MELF ABI and binary toolchain +.SH DESCRIPTION +LibMCPU is a software CPU and system-programming toolchain built on LibMPU and +LibMPUIO. It defines the MCPU instruction set, register file, segmented PTR64 +addressing, CALL and lifecycle ABIs, the MELF object/executable format, startup +dynamic linking and host-side execution supervisor. +.PP +The current toolchain reads MELF ET_REL, ET_EXEC and ET_DYN objects. Static +executables use ABI v1. Historical explicit-preload dynamic files use ABI v2. +The current normal dynamic contract is frozen MELF ABI v3 with .dynamic, +DT_SONAME and DT_NEEDED. Incompatible execution-format changes require a new +MELF ABI number; auxiliary non-ALLOC development metadata does not. +.SH ARCHITECTURAL ADDRESS SPACES +PTR64 uses bits 63..60 as a segment selector and bits 59..0 as byte offset. +Current selectors are DATA=0, STACK=1, PROGRAM=2, CONTROL=3, INPUT=4, OUTPUT=5, +TRACE=6 and HEAP=7. PROGRAM, CONTROL and INPUT are guest read-only; DATA, +STACK, OUTPUT, TRACE and HEAP are writable according to the runtime contract. +.SH PROGRAM STATUS WORD +PSW bits 0..7 form the writable CPU flag byte and bits 8..15 form the writable +MPU flag byte. SETF/CLRF change exactly one named flag in bits 0..15; +CLRCPUF clears bits 0..7 and CLRMPUF clears bits 8..15. HF, TF, IF and MODE +are execution/control state and are not writable through these arithmetic/status +flag instructions. JSET/JCLR retain their wider read-only selector contract. +.SH LOW-LEVEL LIFECYCLE +Every ET_EXEC has mandatory _start and _life lifecycle symbols; e_entry always +addresses _start. An executable may place _start in optional .init and may +provide optional .fini/_fini. END returns control to the supervisor at managed +INIT, LIFE and FINI boundaries. A low-level executable owns these lifecycle +symbols itself and receives no automatic HLL CRT. +.SH HLL RUNTIME ABI V1 +Final executable linking also recognizes compiler-normalized HLL complete-program +objects. A PROGRAM defining ET_REL contains strong GLOBAL/FUNC main and fini in +one object. A MODEL defining ET_REL contains strong GLOBAL/FUNC init, life and +fini in one object. PROGRAM main is incompatible with MODEL init/life, and HLL +complete-program roots cannot be mixed with low-level _start/_life ownership. +This is an object-level contract: if a source language makes fini optional, its +compiler must synthesize an empty strong fini while normalizing the ET_REL. +.PP +The binary signatures are: +.nf +PROGRAM: int32 main(int64 argc, PTR64 argv) + void fini(void) +MODEL: int32 init(int64 argc, PTR64 argv) + int32 life(void) + void fini(void) +.fi +.PP +The loader constructs one persistent 32-byte McpuStartup v1 object before +_start. It contains version, size, uint64 argc, PTR64 argv and a reserved zero +field. CRT validates this object, requires argc to fit target signed int64, +reuses the loader-owned persistent argv array, and invokes main/init through the +ordinary CALL ABI. HLL functions return with RET; END and EXIT remain CRT/MELF +lifecycle operations. +.PP +MODEL life returns INT32_MIN (-2147483648) to continue through exactly one LIFE +END boundary, zero for normal completion, or a positive status for an error +exit. Other negative values are reserved by HLL Runtime ABI v1. +.SH HLL CRT +For final static or dynamic ET_EXEC links, mcpu-ld classifies the explicit input +ET_REL objects before library extraction. PROGRAM selects crt0.o; MODEL selects +crt1.o. Both forms also insert crti.o before user objects and crtn.o after user +objects. The objects are loaded only from: +.nf +<MCPU-root>/lib/mcpu/ +.fi +A missing or invalid required CRT object is a fatal link error. There is no +fallback to another root and no fallback from HLL to low-level linking. +.PP +Startup calls __mcpu_libc_init and __mcpu_init_array before entering the HLL +program/model. Graceful HLL finalization calls user fini, __mcpu_fini_array and +__mcpu_libc_fini before the final FINI END. A failure before runtime startup +completes remains PRE_RUNTIME and skips all HLL finalizers. Normal DT_NEEDED +MCPU Libc dependencies may provide the four internal runtime hooks; explicit +preload interposition may not replace them. +.SH TOOLCHAIN +.TP +.BR mcpu-as (1) +Assembler producing MELF ET_REL. +.TP +.BR mcpu-ld (1) +Static/dynamic final linker and partial ET_REL linker. +.TP +.BR mcpu-run (1) +Loader, startup linker, execution supervisor and host-side test bench. +.TP +.BR mcpu-readelf (1) +Structured read-only MELF inspection. +.TP +.BR mcpu-ar (1) +Deterministic UNIX static-archive manager. +.TP +.BR mcpu-nm (1) +Symbol inspection for MELF files and archives. +.TP +.BR mcpu-objdump (1) +Section, relocation and instruction disassembly/inspection. +.TP +.BR mcpu-strip (1) +Conservative removal of non-runtime symbol metadata. +.TP +.BR mcpu-objcopy (1) +Safe MELF-to-MELF metadata copy/edit operations. +.SH INSTALLATION LAYOUT +For the conventional configuration --prefix=/usr --libdir=/usr/lib64, +private commands live below /usr/lib64/mcpu/bin and public command names are +symbolic links in /usr/bin. The runtime root is relocatable and is derived from +the real command location unless an explicit --sys-root is used by tools that +support it. +.PP +The library-search configuration chain used by mcpu-ld and mcpu-run is: +.nf +<runtime-root>/etc/mcpu-ld.conf +/etc/mcpu/mcpu-ld.conf +$HOME/.mcpu/etc/mcpu-ld.conf +.fi +.PP +The canonical target CRT is installed under: +.nf +<runtime-root>/lib/mcpu/crt0.o +<runtime-root>/lib/mcpu/crt1.o +<runtime-root>/lib/mcpu/crti.o +<runtime-root>/lib/mcpu/crtn.o +.fi +.SH MELF AND DEBUG METADATA +MELF is the native MCPU object and executable container; the toolchain does not +need flat firmware images for normal execution. Development/debug information +can be represented as optional non-ALLOC metadata outside PT_LOAD and outside +the runtime dynamic-link contract. Such metadata can evolve under its own +format version and can be removed without changing frozen MELF ABI v3 or guest +execution semantics. +.SH DOCUMENTATION +The LibMCPU release contains the maintained Russian PDF references: +.nf +doc/libmcpu-bytecode-architecture-ru.pdf +doc/libmcpu-melf-abi-ru.pdf +.fi +Editable XeLaTeX sources are under doc/src/. +.SH FILES +.TP +.I /usr/include/libmcpu.h +Public CPU architecture and operation declarations. +.TP +.I /usr/include/mcpu-elf.h +Public MELF constants and record declarations. +.TP +.I /usr/lib64/pkgconfig/libmcpu.pc +Pkg-config metadata for a conventional /usr,/usr/lib64 installation. +.SH SEE ALSO +.BR mcpu-as (1), +.BR mcpu-ld (1), +.BR mcpu-run (1), +.BR mcpu-readelf (1), +.BR mcpu-ar (1), +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR mcpu-strip (1), +.BR mcpu-objcopy (1) diff --git a/man/mcpu-ar.1 b/man/mcpu-ar.1 new file mode 100644 index 0000000..3de29f8 --- /dev/null +++ b/man/mcpu-ar.1 @@ -0,0 +1,75 @@ +.TH MCPU-AR 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-ar \- create and maintain deterministic MCPU static archives +.SH SYNOPSIS +.B mcpu-ar +.RI [ options ] " operation archive " [ member ...] +.SH DESCRIPTION +.B mcpu-ar +creates and maintains deterministic UNIX ar archives whose ordinary members +are MCPU MELF ET_REL objects. Member order is preserved. A standard +System-V/GNU-style archive symbol index can be built with the s operation or +modifier and is used by +.BR mcpu-ld (1) +for selective extraction. +.SH OPERATIONS +.TP +.B r +Replace matching members in place or append members not already present. +.TP +.B q +Append members without replacement. +.TP +.B d +Delete named members. +.TP +.B t +List members. +.TP +.B p +Write selected member contents to standard output. +.TP +.B x +Extract selected members into the current directory. +.TP +.B s +Rebuild the archive symbol index. +.SH MODIFIERS +.TP +.B c +Accepted for ar compatibility; archive creation is quiet. +.TP +.B s +Write or rebuild the standard archive symbol index. +.TP +.B v +Verbose operation. +.PP +Operation and modifiers may be combined, for example rcs, tv or xv. +.SH OPTIONS +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH EXIT STATUS +Zero on success; nonzero on malformed archives, invalid members, I/O or command +errors. +.SH EXAMPLES +.nf +mcpu-ar rcs libsimple.a add-one.o add-two.o value.o +mcpu-ar t libsimple.a +mcpu-ar tv libsimple.a +mcpu-ar x libsimple.a add-one.o +mcpu-ar s libsimple.a +.fi +.SH SEE ALSO +.BR mcpu-ld (1), +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/mcpu-as.1 b/man/mcpu-as.1 new file mode 100644 index 0000000..c4c73be --- /dev/null +++ b/man/mcpu-as.1 @@ -0,0 +1,85 @@ +.TH MCPU-AS 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-as \- assemble MCPU source into a MELF relocatable object +.SH SYNOPSIS +.B mcpu-as +.RI [ options ] " -o output.o input.s" +.SH DESCRIPTION +.B mcpu-as +reads one UTF-8 MCPU assembler source and writes one MELF +.B ET_REL +relocatable object. The assembler does not have a separate compile-only mode: +ET_REL is its normal and only object output. Input and the +.B -o +option may appear in either order. +.PP +The assembler records the symbols, sections and relocations explicitly described +by the source; it does not choose PROGRAM versus MODEL and does not synthesize +HLL CRT objects. Final lifecycle ownership, automatic HLL CRT selection, +STACK and HEAP program headers are resolved later by +.BR mcpu-ld (1). +.SH OPTIONS +.TP +.BI -o " file" +Write the relocatable object to +.IR file . +This option is mandatory. +.TP +.BR -v ", " --verbose +Print the MELF ET_REL output-object summary after successful assembly. +The default successful invocation is quiet. +.TP +.BR -h ", " --help +Display command-line help and exit. +.SH PSW FLAG INSTRUCTIONS +The assembler accepts explicit arithmetic/status flag control instructions: +.nf +SETF flag +CLRF flag +CLRCPUF +CLRMPUF +.fi +.PP +.B SETF +and +.B CLRF +accept the named flags AF, CF, OF, SF, PF, ZF, RF, VF, DOMF, SNGF, OVFF, +UDFF, TLSF, PLSF, INDF and INXF, corresponding to PSW bits 0 through 15. +HF, TF and IF remain readable branch selectors but are not writable through +these instructions; MODE is a multi-bit field and has no selector. +.B CLRCPUF +clears PSW[7:0], while +.B CLRMPUF +clears PSW[15:8]. Both preserve all control/execution state above bit 15. +.SH SOURCE LOCATION CONTROL +The assembler accepts the source directive +.B #line +with a decimal line number and an optional quoted file name. It is intended +for preprocessor output and changes subsequent diagnostic source locations; +it does not become part of the MELF object. +.SH FILES +.TP +.I *.s +MCPU assembler source. +.TP +.I *.o +Conventional name for MELF ET_REL output. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH EXIT STATUS +Zero on success; nonzero on syntax, semantic, I/O or object-generation error. +.SH EXAMPLES +.nf +mcpu-as -o main.o main.s +mcpu-as helper.s -o helper.o +.fi +.SH SEE ALSO +.BR mcpu-ld (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/mcpu-ld.1 b/man/mcpu-ld.1 new file mode 100644 index 0000000..05172bd --- /dev/null +++ b/man/mcpu-ld.1 @@ -0,0 +1,180 @@ +.TH MCPU-LD 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-ld \- link MCPU MELF objects, archives and shared libraries +.SH SYNOPSIS +.B mcpu-ld +.RI [ link-options ] " -o output input ..." +.SH DESCRIPTION +.B mcpu-ld +links MELF ET_REL objects and MCPU static libraries. It can produce a +partially linked ET_REL object, a fully static ABI-v1 ET_EXEC, a frozen ABI-v3 +startup-dynamic ET_EXEC, or an ABI-v3 ET_DYN shared object. +.PP +The default output, when none of +.BR -r , +.B --shared +or +.B --dynamic +is present, is a fully static ABI-v1 ET_EXEC. +.SH OUTPUT MODES +.TP +.B -r +Produce one partial-link MELF ET_REL object. Unresolved strong symbols and +RELA records may remain for a later final link. STACK, HEAP, SONAME and shared +objects are not part of this mode. +.TP +.B --shared +Produce a frozen ABI-v3 MELF ET_DYN shared object. A logical +.B --soname +is mandatory. Shared objects may contain only .text/.data/.bss allocated +sections and own no lifecycle, model spaces, STACK or HEAP. +.TP +.B --dynamic +Produce a frozen ABI-v3 startup-dynamic MELF ET_EXEC. Normal dependencies are +recorded as DT_NEEDED SONAMEs. +.SH AUTOMATIC HLL CRT +For final static or dynamic ET_EXEC output, mcpu-ld examines the explicitly +listed ET_REL input objects before processing -l libraries. It recognizes two +compiler-normalized complete-program forms: +.nf +PROGRAM: strong GLOBAL/FUNC main + fini in one ET_REL +MODEL: strong GLOBAL/FUNC init + life + fini in one ET_REL +.fi +.PP +PROGRAM and MODEL roots are mutually exclusive. Incomplete or split MODEL +definitions are rejected, as are split PROGRAM main/fini definitions. A final +link that mixes HLL roots with low-level _start/_life lifecycle ownership is an +error. The linker does not use library members or shared-library definitions to +classify the complete program. +.PP +After classification, mcpu-ld automatically inserts: +.nf +PROGRAM: crt0.o crti.o <explicit input objects> crtn.o +MODEL: crt1.o crti.o <explicit input objects> crtn.o +.fi +The CRT objects are loaded from <MCPU-root>/lib/mcpu, where <MCPU-root> is the +active installation/system root. Missing or unreadable required CRT objects +are fatal errors. There is no fallback CRT search in another root and no +fallback from an HLL link to low-level linking. +.PP +Low-level ET_EXEC inputs that own _start/_life themselves are linked without HLL +CRT insertion. The -r and --shared modes never perform HLL complete-program +classification or CRT insertion. +.SH SYSTEM ROOT AND CONFIGURATION +.TP +.B --no-config +Do not read mcpu-ld.conf files. The executable-derived runtime root remains +active and supplies its default lib directory. +.TP +.BI --sys-root= PATH +Use PATH as the MCPU system root and do not read configuration files. PATH/lib +becomes the system library directory. A relative PATH is resolved from the +invocation working directory. +.TP +.BI --sys-root " PATH" +Equivalent two-argument form. +.TP +.B --dump-config +Print the effective MCPU configuration and exit. +.TP +.B --print-search-dirs +Print effective library search directories in search order and exit. +.SH LIBRARY OPTIONS +.TP +.BI -L " dir" +.TQ +.BI -L dir +Add an explicit library search directory. Explicit -L directories precede +the configured system library path. +.TP +.BI -l " name" +.TQ +.BI -l name +Link against libNAME. ABI-v1 and -r search only libNAME.a. ABI-v3 dynamic +and shared links search each directory in directory-major order, trying +libNAME.so before libNAME.a. +.TP +.B --static +Force every -l lookup to use libNAME.a without changing the selected output +mode. Static archive members are copied into the result and do not create +DT_NEEDED entries. +.SH SHARED-LIBRARY IDENTITY +.TP +.BI --soname= NAME +.TQ +.BI --soname " NAME" +Set the mandatory ABI-v3 ET_DYN SONAME. NAME is a logical library identity +and must not contain a slash. +.SH EXECUTABLE LAYOUT OPTIONS +.TP +.BI -o " file" +Write the result to file. +.TP +.BI --stack-size= size +Set fixed executable STACK size. The value must be positive and 16-byte +aligned. The default is 1048576 bytes. +.TP +.BI --heap-size= size +Set initial HEAP size. Default: 1048576 bytes. +.TP +.BI --heap-limit= size +Set immutable maximum HEAP size. Default: 1048576 bytes. +.TP +.B --no-heap +Omit PT_MCPU_HEAP from an executable. +.TP +.BR -v ", " --verbose +Print the MELF output-object summary after a successful link. +The default successful invocation is quiet. +.TP +.BR -h ", " --help +Display command-line help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.PP +Size operands are bytes by default. K/KiB, M/MiB and G/GiB are accepted; +K, M and G use binary multipliers. +.SH ARCHIVES AND SYMBOL RESOLUTION +Static libraries are ordinary deterministic UNIX ar containers whose ordinary +members are MELF ET_REL objects. Archive members are extracted selectively +when they satisfy currently unresolved strong GLOBAL symbols. An unresolved +WEAK reference does not by itself pull an archive member. +.PP +For ABI-v3 dynamic output, selecting an ET_DYN provider records its SONAME as +DT_NEEDED. Selecting an archive provider links the required members +statically and records no dependency for that archive. +.SH FILES +.TP +.I <runtime-root>/lib/mcpu/crt0.o, crt1.o, crti.o, crtn.o +Required automatic HLL CRT objects for PROGRAM/MODEL final executable links. +.TP +.I <runtime-root>/etc/mcpu-ld.conf +Relocatable-tree configuration installed with LibMCPU. +.TP +.I /etc/mcpu/mcpu-ld.conf +Optional machine-wide override. +.TP +.I $HOME/.mcpu/etc/mcpu-ld.conf +Optional per-user override. +.SH EXIT STATUS +Zero on success; nonzero on command-line, input-validation, symbol-resolution, +relocation or output error. +.SH EXAMPLES +.nf +mcpu-ld --stack-size=64K --no-heap -o program main.o helper.o +mcpu-ld -r -o combined.o a.o b.o +mcpu-ld --shared --soname=libfoo.so.1 -o libfoo.so.1.0.0 foo.o +mcpu-ld --dynamic -L ./lib -lfoo -o program main.o +mcpu-ld --dynamic --static -L ./lib -lchoice -o program main.o +.fi +.SH SEE ALSO +.BR mcpu-as (1), +.BR mcpu-ar (1), +.BR mcpu-run (1), +.BR mcpu-readelf (1), +.BR libmcpu (7) diff --git a/man/mcpu-nm.1 b/man/mcpu-nm.1 new file mode 100644 index 0000000..abf4f92 --- /dev/null +++ b/man/mcpu-nm.1 @@ -0,0 +1,66 @@ +.TH MCPU-NM 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-nm \- list symbols from MCPU MELF files and archives +.SH SYNOPSIS +.B mcpu-nm +.RI [ options ] " MELF-file(s)" +.SH DESCRIPTION +.B mcpu-nm +lists symbols from MELF ET_REL objects, ET_EXEC executables, ET_DYN shared +objects and deterministic MCPU static archives. By default it reads .symtab +and sorts symbols by name. For ABI-v2/v3 runtime symbols use +.BR -D . +.SH OPTIONS +.TP +.BR -a ", " --debug-syms +Display all named symbols, including FILE and SECTION symbols. +.TP +.BR -g ", " --extern-only +Display only external GLOBAL and WEAK symbols. +.TP +.BR -u ", " --undefined-only +Display only undefined symbols. +.TP +.BR -n ", " --numeric-sort +Sort symbols numerically by value. +.TP +.BR -r ", " --reverse-sort +Reverse the selected sort order. +.TP +.BR -p ", " --no-sort +Preserve symbol-table order. +.TP +.BR -D ", " --dynamic +Read .dynsym instead of .symtab. +.TP +.BR -A ", " --print-file-name +Prefix each output line with the input file name. Archive output identifies +the archive and member. +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH SYMBOL TYPE LETTERS +As with traditional nm output, upper-case type letters identify external +symbols and lower-case letters local symbols. The letter also reflects the +MCPU section class, for example T/t for PROGRAM text and D/d for DATA. +.SH EXIT STATUS +Zero on successful inspection; nonzero for inaccessible or malformed input. +.SH EXAMPLES +.nf +mcpu-nm file.o +mcpu-nm -n file.o +mcpu-nm -D libfoo.so.1 +mcpu-nm -A -D -g libfoo.so.1 +mcpu-nm -A libsimple.a +.fi +.SH SEE ALSO +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/mcpu-objcopy.1 b/man/mcpu-objcopy.1 new file mode 100644 index 0000000..d6efeac --- /dev/null +++ b/man/mcpu-objcopy.1 @@ -0,0 +1,77 @@ +.TH MCPU-OBJCOPY 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-objcopy \- safely copy and edit MCPU MELF metadata +.SH SYNOPSIS +.B mcpu-objcopy +.RI [ options ] " INPUT OUTPUT" +.SH DESCRIPTION +.B mcpu-objcopy +is a MELF-to-MELF structural copier and metadata editor. It is intentionally +not a ROM-image conversion tool: there are no flat binary, Intel HEX, S-record, +VMA/LMA, start-address or section-flag conversion modes. +.PP +The editor protects the frozen MELF ABI-v3 execution contract. Runtime-visible +allocated sections, dynamic-linking metadata, runtime relocations and MCPU +target/lifecycle data cannot be removed, renamed or updated through the safe +metadata interface. +.SH OPTIONS +.TP +.BI -j " NAME" +.TQ +.BI --only-section= NAME +Copy only named sections. Repeatable. The operation is rejected if filtering +would discard runtime-visible, dynamic-linking or ET_REL link-critical data. +.TP +.BI -R " NAME" +.TQ +.BI --remove-section= NAME +Remove a safe non-runtime section. +.TP +.BI --dump-section= NAME=FILE +Write section contents to FILE. For archive input this operation is rejected +as ambiguous because one destination does not identify an archive member. +.TP +.BI --add-section= NAME=FILE +Add a new non-allocated SHT_PROGBITS metadata section whose contents are read +from FILE. Reserved runtime section names are rejected. +.TP +.BI --update-section= NAME=FILE +Replace contents of a safe non-allocated SHT_PROGBITS section. +.TP +.BI --rename-section= OLD=NEW +Rename a safe non-allocated section. Reserved runtime names are protected. +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH STRUCTURAL INVARIANTS +Metadata-only edits rebuild .shstrtab and remap affected section references, +including retained symbol st_shndx values, while preserving the allocated +image and program headers. The non-allocated metadata tail may be repacked. +.SH ARCHIVES +Deterministic MCPU archives are transformed member-by-member and keep their +archive symbol-index semantics. Unknown non-MELF archive members are preserved +according to the archive editor policy. +.SH EXIT STATUS +Zero on success; nonzero on invalid operations, safety-policy violations, +malformed input or I/O error. +.SH EXAMPLES +.nf +mcpu-objcopy input.o output.o +mcpu-objcopy --add-section=.note.build=build.txt input.o output.o +mcpu-objcopy --update-section=.note.build=new.txt input.o output.o +mcpu-objcopy --rename-section=.note.build=.note.release input.o output.o +mcpu-objcopy -R .note.release input.o output.o +mcpu-objcopy --dump-section=.data=data.bin input.o output.o +.fi +.SH SEE ALSO +.BR mcpu-strip (1), +.BR mcpu-objdump (1), +.BR mcpu-readelf (1), +.BR libmcpu (7) diff --git a/man/mcpu-objdump.1 b/man/mcpu-objdump.1 new file mode 100644 index 0000000..92f17a0 --- /dev/null +++ b/man/mcpu-objdump.1 @@ -0,0 +1,69 @@ +.TH MCPU-OBJDUMP 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-objdump \- display sections, relocations and MCPU instructions +.SH SYNOPSIS +.B mcpu-objdump +.RI [ options ] " MELF-file(s)" +.SH DESCRIPTION +.B mcpu-objdump +inspects MELF objects, executables, shared objects and deterministic archives. +Its disassembler uses the same LibMCPU instruction decoder used by execution, +so the toolchain does not maintain a second ISA parser. Symbol and relocation +information is used to annotate disassembly without rewriting raw operand bytes. +.SH OPTIONS +.TP +.BR -d ", " --disassemble +Disassemble executable PROGBITS sections. +.TP +.BR -D ", " --disassemble-all +Attempt disassembly of all selected nonempty PROGBITS sections. +.TP +.BR -r ", " --reloc +Display relocation entries. During disassembly, relocations are associated +with the instruction containing the relocated field; relocations from other +sections remain visible as tables. +.TP +.BR -s ", " --full-contents +Display raw contents of selected sections. +.TP +.BR -t ", " --syms +Display .symtab symbols. +.TP +.BR -T ", " --dynamic-syms +Display .dynsym symbols. +.TP +.BR -h ", " --section-headers +Display section headers. +.TP +.BI -j " NAME" +.TQ +.BI --section= NAME +Restrict section-oriented output to NAME. Repeatable. +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.PP +At least one display option is required. Undecodable bytes are printed as +.byte data and inspection continues. +.SH EXIT STATUS +Zero on successful inspection; nonzero for invalid options or malformed input. +.SH EXAMPLES +.nf +mcpu-objdump -h file.o +mcpu-objdump -t -r -d file.o +mcpu-objdump -T -d -r libfoo.so.1 +mcpu-objdump -s -j .data file.o +mcpu-objdump -d libsimple.a +.fi +.SH SEE ALSO +.BR mcpu-readelf (1), +.BR mcpu-nm (1), +.BR mcpu-objcopy (1), +.BR libmcpu (7) diff --git a/man/mcpu-readelf.1 b/man/mcpu-readelf.1 new file mode 100644 index 0000000..df770bd --- /dev/null +++ b/man/mcpu-readelf.1 @@ -0,0 +1,76 @@ +.TH MCPU-READELF 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-readelf \- display structured information from MCPU MELF files +.SH SYNOPSIS +.B mcpu-readelf +.RI "option(s) MELF-file(s)" +.SH DESCRIPTION +.B mcpu-readelf +is a read-only inspector for LibMCPU MELF ABI-v1, ABI-v2 and ABI-v3 files. It +displays headers, sections, symbols, RELA relocations, dynamic dependency +metadata and MCPU-specific target/lifecycle records without modifying input. +.SH OPTIONS +.TP +.BR -a ", " --all +Equivalent to -h -l -S -s -r -d -A. +.TP +.BR -e ", " --headers +Equivalent to -h -l -S. +.TP +.BR -h ", " --file-header +Display the MELF file header. +.TP +.BR -l ", " --program-headers +Display program headers. +.TP +.B --segments +Alias for --program-headers. +.TP +.BR -S ", " --section-headers +Display section headers. +.TP +.B --sections +Alias for --section-headers. +.TP +.BR -s ", " --symbols +Display .symtab and .dynsym symbol tables when present. +.TP +.B --syms +Alias for --symbols. +.TP +.BR -r ", " --relocs +Display RELA relocation sections. +.TP +.BR -d ", " --dynamic +Display ABI-v3 .dynamic dependency metadata including SONAME and NEEDED. +.TP +.BR -A ", " --arch-specific +Display MCPU target and lifecycle metadata. +.TP +.BR -W ", " --wide +Do not truncate output; accepted for GNU-readelf-style compatibility. +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH EXIT STATUS +Zero when all requested files are read successfully; nonzero for malformed, +unsupported or inaccessible input. +.SH EXAMPLES +.nf +mcpu-readelf -h -S file.o +mcpu-readelf -s -r file.o +mcpu-readelf -l -A program +mcpu-readelf -d libfoo.so.1 +mcpu-readelf -a program +.fi +.SH SEE ALSO +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/mcpu-run.1 b/man/mcpu-run.1 new file mode 100644 index 0000000..65c8f42 --- /dev/null +++ b/man/mcpu-run.1 @@ -0,0 +1,210 @@ +.TH MCPU-RUN 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-run \- load and execute an MCPU MELF executable +.SH SYNOPSIS +.B mcpu-run +.RI [ runtime-options ] " -- executable " [ program-args ...] +.br +.B mcpu-run +.RI [ runtime-options ] " executable " [ program-args ...] +.SH DESCRIPTION +.B mcpu-run +is the LibMCPU loader, startup linker, execution supervisor and host-side test +bench for MELF ET_EXEC images. The form with an explicit +.B -- +separator is preferred. STACK and HEAP sizes are part of the executable MELF +ABI and cannot be overridden by the runner. +.PP +The loader accepts static ABI-v1 executables, historical ABI-v2 explicit +preload files, and frozen ABI-v3 startup-dynamic executables and dependencies. +All startup dependency discovery, symbol selection and runtime relocation is +completed before the first guest instruction. +.SH HLL STARTUP AND LIFECYCLE +An HLL PROGRAM or MODEL executable still enters through the ordinary MELF +_start/_life/_fini lifecycle supplied by the linked CRT. mcpu-run does not +call main, init, life or fini directly. +.PP +Before _start, the loader constructs one persistent McpuStartup v1 area in +STACK containing version, size, argc, PTR64 argv and a reserved zero field. +The argv array and argument strings remain valid for the executable lifetime. +The HLL CRT validates this state and bridges it to the ordinary CALL ABI. +.PP +PROGRAM executes its HLL main once and exits with the returned int32 status. +MODEL performs INIT through init(), then invokes life() once per lifecycle +iteration. INT32_MIN means continue through one LIFE END boundary; zero means +normal completion; a positive value is an error exit status. Other negative +life results are reserved by HLL Runtime ABI v1. +.PP +Graceful HLL exit runs user fini(), __mcpu_fini_array() and +__mcpu_libc_fini() before the final FINI END. Startup failure before the CRT +reaches RUNNING skips those finalizers. A diagnostic --steps stop remains a +supervisor stop and does not synthesize graceful HLL finalization. +.SH SYSTEM OPTIONS +.TP +.B --no-config +Do not read mcpu-ld.conf files while keeping the executable-derived default +system library tree. +.TP +.BI --sys-root= PATH +Use PATH as the MCPU root, suppress configuration files and use PATH/lib as the +system library directory. +.TP +.B --dump-config +Print effective MCPU configuration and exit. +.TP +.B --print-search-dirs +Print effective library search directories and exit. +.SH LIBRARY OPTIONS +These options must precede the executable or explicit -- separator. +.TP +.BI --library-path= DIR[:DIR...] +Add explicit library search directories. The option may be repeated. These +directories precede the effective system library path. +.TP +.BI --ldpreload= a.so:b.so +Preload ABI-v2 or ABI-v3 shared objects. A name without slash is searched; +a spelling containing slash is used as an explicit pathname. +.SH ENVIRONMENT +.TP +.B MCPU_LD_PRELOAD +Colon-separated preload list applied before command-line --ldpreload entries. +For equal-strength definitions the established rightmost priority applies, so +command-line preloads can override environment preloads. +.SH POST-STOP INSPECTION OPTIONS +.TP +.BI --int-reg= r<bits>@<addr> +Print a signed integer register after execution stops. +.TP +.BI --real-reg= r<bits>@<addr> +Print a real register after execution stops. +.TP +.BI --int-data= <bits>@<addr> +.TQ +.BI --real-data= <bits>@<addr> +.TQ +.BI --raw-data= <bytes>@<addr> +Inspect DATA. +.TP +.BI --int-control= <bits>@<addr> +.TQ +.BI --real-control= <bits>@<addr> +.TQ +.BI --raw-control= <bytes>@<addr> +Inspect CONTROL. +.TP +.BI --int-heap= <bits>@<addr> +.TQ +.BI --real-heap= <bits>@<addr> +.TQ +.BI --raw-heap= <bytes>@<addr> +Inspect HEAP. +.TP +.BI --int-input= <bits>@<addr> +.TQ +.BI --real-input= <bits>@<addr> +.TQ +.BI --raw-input= <bytes>@<addr> +Inspect INPUT. +.TP +.BI --int-output= <bits>@<addr> +.TQ +.BI --real-output= <bits>@<addr> +.TQ +.BI --raw-output= <bytes>@<addr> +Inspect OUTPUT. +.TP +.BI --int-trace= <bits>@<addr> +.TQ +.BI --real-trace= <bits>@<addr> +.TQ +.BI --raw-trace= <bytes>@<addr> +Inspect TRACE. +.SH LIFECYCLE CONTROL +.TP +.BI --digits= n +Set mantissa digits for real output. Default: 80. +.TP +.B --init-only +Execute INIT through its END boundary, then stop. +.TP +.BI --steps= n +Stop after n LIFE END boundaries. Without this option normal diagnostic +stepping stops at the first LIFE END boundary. +.TP +.BR -h ", " --help +Display command-line help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH STAGE ACTIONS +The following repeatable options run host actions at lifecycle boundaries: +.TP +.BI --before-run= action +After loading, startup linking, relocations and startup-area preparation, but +before the first guest instruction. +.TP +.BI --after-init= action +After INIT END, or once at the logical empty INIT boundary when .init is absent. +.TP +.BI --on-step= action +After every LIFE END boundary. +.TP +.BI --after-run= action +After final normal execution stop; graceful FINI has already completed. +.PP +Actions execute in command-line order. Supported action forms are: +.nf +print-int-regs:r<bits>@<addr>[,...] +print-real-regs:r<bits>@<addr>[,...] +set-int-regs:r<bits>@<addr>=<value>[,...] +set-real-regs:r<bits>@<addr>=<value>[,...] +print-int-<memory>:<bits>@<addr>[,...] +print-real-<memory>:<bits>@<addr>[,...] +set-int-<memory>:<bits>@<addr>=<value>[,...] +set-real-<memory>:<bits>@<addr>=<value>[,...] +print-raw-<memory>:<bytes>@<addr> +.fi +.PP +.I memory +is data, control, heap, input, output or trace. Scalar print actions print the +comma-separated objects on one line. Raw actions contain exactly one dump; +repeat the stage option for multiple dumps. There are no set-raw-* actions. +Set values are parsed by LibMPUIO mpu_sscanf(). Step-number action filters are +not implemented. +.PP +Raw byte counts accept K/KiB, M/MiB and G/GiB binary suffixes. +.SH LIFECYCLE AND EXIT +END returns control to the supervisor at managed INIT/LIFE/FINI boundaries. +A graceful guest EXIT may enter optional FINI before the final stop. A +diagnostic --steps stop does not synthesize a graceful EXIT and therefore does +not by itself invoke FINI. +.SH FILES +.TP +.I <runtime-root>/etc/mcpu-ld.conf +Installed relocatable-tree library-search configuration. +.TP +.I /etc/mcpu/mcpu-ld.conf +Optional machine-wide override. +.TP +.I $HOME/.mcpu/etc/mcpu-ld.conf +Optional per-user override. +.SH EXIT STATUS +For a normally completed guest program the runner returns the guest exit +status. Loader, startup-linker, option or runtime errors terminate with a +nonzero host-side status. +.SH EXAMPLES +.nf +mcpu-run -- program arg1 arg2 +mcpu-run --library-path=./lib -- program +MCPU_LD_PRELOAD=libhook.so mcpu-run -- program +mcpu-run --steps=20 --on-step=print-real-trace:128@0,128@16 -- pendulum +.fi +.SH SEE ALSO +.BR mcpu-ld (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/mcpu-strip.1 b/man/mcpu-strip.1 new file mode 100644 index 0000000..f36e9aa --- /dev/null +++ b/man/mcpu-strip.1 @@ -0,0 +1,56 @@ +.TH MCPU-STRIP 1 "October 2026" "LibMCPU" "User Commands" + +.SH NAME +mcpu-strip \- safely remove non-runtime symbol metadata from MELF +.SH SYNOPSIS +.B mcpu-strip +.RI [ options ] " MELF-file" +.SH DESCRIPTION +.B mcpu-strip +is deliberately conservative. It removes ordinary non-allocated SHT_SYMTAB +metadata from ET_EXEC and ET_DYN only when no retained section depends on it, +and removes an associated ordinary string table only when it becomes +unreferenced. +.PP +ET_REL symbol tables are preserved because they are part of later link +semantics. Dynamic-linking tables, RELA sections, MCPU target/lifecycle +metadata, allocated/model sections and program headers are always preserved. +Current LibMCPU final-image writers are already compact, so stripping a normal +current ET_EXEC or ET_DYN may intentionally be a byte-for-byte no-op. +.SH OPTIONS +.TP +.BI -o " file" +.TQ +.BI --output= FILE +Write the result to FILE instead of replacing the input in place. +.TP +.B --help +Display help and exit. +.TP +.B --version +Display the program name, MCPU toolchain identity and version, then exit. +.TP +.B --dump-version +Display only the MCPU package version number and exit. +.SH IN-PLACE SAFETY +In-place replacement is performed through a temporary file in the same +directory followed by rename, while preserving the original file mode. +Allocated image bytes and program-header layout are not repacked by the safe +metadata policy. +.SH ARCHIVES +Deterministic MCPU static archives are processed member-by-member; link-critical +ET_REL symbol information remains intact. +.SH EXIT STATUS +Zero on success; nonzero on malformed input, dependency-safety or I/O error. +.SH EXAMPLES +.nf +mcpu-strip program +mcpu-strip -o program.stripped program +mcpu-strip libfoo.so.1 +mcpu-strip libsimple.a +.fi +.SH SEE ALSO +.BR mcpu-objcopy (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/Makefile.am b/man/ru/Makefile.am new file mode 100644 index 0000000..21e1639 --- /dev/null +++ b/man/ru/Makefile.am @@ -0,0 +1,18 @@ + +LANG = ru + +mandir = @mandir@/$(LANG) + +MAN1 = mcpu-as.1 \ + mcpu-ld.1 \ + mcpu-run.1 \ + mcpu-readelf.1 \ + mcpu-ar.1 \ + mcpu-nm.1 \ + mcpu-objdump.1 \ + mcpu-strip.1 \ + mcpu-objcopy.1 + +MAN7 = libmcpu.7 + +dist_man_MANS = $(MAN1) $(MAN7) diff --git a/man/ru/libmcpu.7 b/man/ru/libmcpu.7 new file mode 100644 index 0000000..8003296 --- /dev/null +++ b/man/ru/libmcpu.7 @@ -0,0 +1,157 @@ +.TH LIBMCPU 7 "Октябрь 2026" "LibMCPU" "Архитектура и инструментарий MCPU" + +.SH ИМЯ +libmcpu \- виртуальный CPU MCPU, MELF ABI и бинарный toolchain +.SH ОПИСАНИЕ +LibMCPU — программный CPU и system-programming toolchain на базе LibMPU и +LibMPUIO. Он определяет ISA MCPU, register file, segmented PTR64, CALL и +lifecycle ABI, объектный/исполняемый формат MELF, startup dynamic linking и +host-side execution supervisor. +.PP +Toolchain читает MELF ET_REL, ET_EXEC и ET_DYN. Статические executable +используют ABI v1; historical explicit-preload dynamic files — ABI v2; текущий +normal dynamic contract — frozen MELF ABI v3 с .dynamic, DT_SONAME и DT_NEEDED. +Несовместимое изменение execution format требует нового MELF ABI number; +дополнительные non-ALLOC development metadata его не требуют. +.SH АРХИТЕКТУРНЫЕ ПРОСТРАНСТВА +PTR64 использует bits 63..60 как segment selector и 59..0 как byte offset. +Селекторы: DATA=0, STACK=1, PROGRAM=2, CONTROL=3, INPUT=4, OUTPUT=5, TRACE=6, +HEAP=7. PROGRAM, CONTROL и INPUT guest read-only; DATA, STACK, OUTPUT, TRACE и +HEAP writable в рамках runtime contract. +.SH СЛОВО СОСТОЯНИЯ PSW +PSW bits 0..7 образуют writable CPU flag byte, а bits 8..15 — writable MPU +flag byte. SETF/CLRF изменяют ровно один именованный flag в диапазоне 0..15; +CLRCPUF очищает bits 0..7, CLRMPUF — bits 8..15. HF, TF, IF и MODE относятся +к execution/control state и этими arithmetic/status instructions не записываются. +JSET/JCLR сохраняют более широкий read-only selector contract. +.SH НИЗКОУРОВНЕВЫЙ LIFECYCLE +Каждый ET_EXEC имеет обязательные _start и _life; e_entry всегда адресует +_start. Executable может размещать _start в optional .init и иметь optional +.fini/_fini. END возвращает управление supervisor на управляемых границах INIT, +LIFE и FINI. Низкоуровневый executable сам владеет lifecycle symbols и не +получает automatic HLL CRT. +.SH HLL RUNTIME ABI V1 +Final executable link также распознаёт compiler-normalized HLL complete-program +objects. PROGRAM defining ET_REL содержит strong GLOBAL/FUNC main и fini в одном +object. MODEL defining ET_REL содержит strong GLOBAL/FUNC init, life и fini в +одном object. PROGRAM main несовместим с MODEL init/life; HLL complete-program +roots нельзя смешивать с низкоуровневым владением _start/_life. +Это object-level contract: если source language делает fini optional, compiler +обязан синтезировать empty strong fini при нормализации ET_REL. +.PP +Binary signatures: +.nf +PROGRAM: int32 main(int64 argc, PTR64 argv) + void fini(void) +MODEL: int32 init(int64 argc, PTR64 argv) + int32 life(void) + void fini(void) +.fi +.PP +Перед _start loader один раз создаёт постоянный 32-byte McpuStartup v1: +version, size, uint64 argc, PTR64 argv и reserved zero field. CRT проверяет эту +структуру, требует представимость argc в target signed int64, повторно использует +loader-owned persistent argv и вызывает main/init через обычный CALL ABI. HLL +functions завершаются RET; END и EXIT остаются операциями CRT/MELF lifecycle. +.PP +MODEL life возвращает INT32_MIN (-2147483648) для продолжения через ровно одну +LIFE END boundary, zero для нормального завершения модели и positive status для +error exit. Остальные отрицательные значения зарезервированы HLL Runtime ABI v1. +.SH HLL CRT +Для final static/dynamic ET_EXEC link mcpu-ld классифицирует явные входные +ET_REL objects до извлечения библиотек. PROGRAM выбирает crt0.o, MODEL — crt1.o; +в обоих случаях crti.o вставляется перед user objects, а crtn.o после них. +Объекты загружаются только из: +.nf +<MCPU-root>/lib/mcpu/ +.fi +Отсутствующий или повреждённый обязательный CRT object является fatal link +error. Перехода к другому root и fallback из HLL в low-level linking нет. +.PP +Startup вызывает __mcpu_libc_init и __mcpu_init_array до входа в HLL +program/model. Graceful HLL finalization вызывает user fini, +__mcpu_fini_array и __mcpu_libc_fini перед финальным FINI END. Ошибка до +завершения runtime startup остаётся PRE_RUNTIME и пропускает HLL finalizers. +Обычная DT_NEEDED MCPU Libc dependency может предоставлять четыре internal +runtime hooks; explicit preload не может их подменять. +.SH TOOLCHAIN +.TP +.BR mcpu-as (1) +Assembler MELF ET_REL. +.TP +.BR mcpu-ld (1) +Final/static/dynamic и partial ET_REL linker. +.TP +.BR mcpu-run (1) +Loader, startup linker, execution supervisor и host-side test bench. +.TP +.BR mcpu-readelf (1) +Structured read-only MELF inspection. +.TP +.BR mcpu-ar (1) +Deterministic UNIX static archives. +.TP +.BR mcpu-nm (1) +Symbol inspection. +.TP +.BR mcpu-objdump (1) +Sections, relocations и disassembly. +.TP +.BR mcpu-strip (1) +Conservative non-runtime symbol stripping. +.TP +.BR mcpu-objcopy (1) +Safe MELF-to-MELF metadata editing. +.SH INSTALLATION LAYOUT +При --prefix=/usr --libdir=/usr/lib64 private commands находятся в +/usr/lib64/mcpu/bin, а public names являются symlinks в /usr/bin. Runtime root +перемещаем и обычно выводится из реального пути executable; инструменты с +--sys-root могут выбрать другой root явно. +.PP +Configuration chain mcpu-ld/mcpu-run: +.nf +<runtime-root>/etc/mcpu-ld.conf +/etc/mcpu/mcpu-ld.conf +$HOME/.mcpu/etc/mcpu-ld.conf +.fi +.PP +Canonical target CRT устанавливается в: +.nf +<runtime-root>/lib/mcpu/crt0.o +<runtime-root>/lib/mcpu/crt1.o +<runtime-root>/lib/mcpu/crti.o +<runtime-root>/lib/mcpu/crtn.o +.fi +.SH MELF И DEBUG METADATA +MELF — native MCPU object/executable container; для обычного исполнения не +нужны flat firmware images. Development/debug information может жить как +необязательная non-ALLOC metadata вне PT_LOAD и runtime dynamic-link contract. +Такая metadata может иметь собственную версию формата и удаляться без изменения +frozen MELF ABI v3 и guest execution semantics. +.SH ДОКУМЕНТАЦИЯ +В release входят русские PDF references: +.nf +doc/libmcpu-bytecode-architecture-ru.pdf +doc/libmcpu-melf-abi-ru.pdf +.fi +Editable XeLaTeX sources находятся в doc/src/. +.SH ФАЙЛЫ +.TP +.I /usr/include/libmcpu.h +Public CPU declarations. +.TP +.I /usr/include/mcpu-elf.h +Public MELF constants/records. +.TP +.I /usr/lib64/pkgconfig/libmcpu.pc +Pkg-config metadata при обычной /usr,/usr/lib64 установке. +.SH СМ. ТАКЖЕ +.BR mcpu-as (1), +.BR mcpu-ld (1), +.BR mcpu-run (1), +.BR mcpu-readelf (1), +.BR mcpu-ar (1), +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR mcpu-strip (1), +.BR mcpu-objcopy (1) diff --git a/man/ru/mcpu-ar.1 b/man/ru/mcpu-ar.1 new file mode 100644 index 0000000..d19db3f --- /dev/null +++ b/man/ru/mcpu-ar.1 @@ -0,0 +1,70 @@ +.TH MCPU-AR 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-ar \- manager deterministic static archives MCPU +.SH СИНТАКСИС +.B mcpu-ar +.RI [ опции ] " операция архив " [ member ...] +.SH ОПИСАНИЕ +.B mcpu-ar +создаёт deterministic UNIX ar archives с MELF ET_REL members. Порядок members +сохраняется. Операция/модификатор s строит System-V/GNU-style symbol index, +который использует +.BR mcpu-ld (1) +при selective extraction. +.SH ОПЕРАЦИИ +.TP +.B r +Заменить existing members на месте или добавить новые. +.TP +.B q +Всегда добавить members без replacement. +.TP +.B d +Удалить named members. +.TP +.B t +Список members. +.TP +.B p +Вывести содержимое member в stdout. +.TP +.B x +Извлечь members в текущий каталог. +.TP +.B s +Перестроить symbol index. +.SH МОДИФИКАТОРЫ +.TP +.B c +Compatibility modifier; создание архива тихое. +.TP +.B s +Записать/rebuild symbol index. +.TP +.B v +Verbose operation. +.PP +Операция и modifiers объединяются: rcs, tv, xv. +.SH ОПЦИИ +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH ПРИМЕРЫ +.nf +mcpu-ar rcs libsimple.a add-one.o add-two.o value.o +mcpu-ar t libsimple.a +mcpu-ar tv libsimple.a +mcpu-ar x libsimple.a add-one.o +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-ld (1), +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-as.1 b/man/ru/mcpu-as.1 new file mode 100644 index 0000000..96d79ed --- /dev/null +++ b/man/ru/mcpu-as.1 @@ -0,0 +1,79 @@ +.TH MCPU-AS 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-as \- ассемблер исходного текста MCPU в перемещаемый объект MELF +.SH СИНТАКСИС +.B mcpu-as +.RI [ опции ] " -o output.o input.s" +.SH ОПИСАНИЕ +.B mcpu-as +читает один исходный файл ассемблера MCPU в UTF-8 и создаёт один перемещаемый +объект MELF +.BR ET_REL . +Отдельного режима compile-only нет: ET_REL является нормальным и единственным +объектным выходом ассемблера. Исходный файл и +.B -o +могут находиться в любом порядке. +.PP +Assembler записывает symbols, sections и relocations, явно заданные исходным +текстом; он не выбирает PROGRAM или MODEL и не синтезирует HLL CRT objects. +Final lifecycle ownership, automatic HLL CRT selection, STACK и HEAP program +headers определяются позднее командой +.BR mcpu-ld (1). +.SH ОПЦИИ +.TP +.BI -o " файл" +Записать объект в указанный файл. Опция обязательна. +.TP +.BR -v ", " --verbose +После успешной сборки вывести краткую сводку о созданном MELF ET_REL object. +По умолчанию успешный запуск ничего не выводит. +.TP +.BR -h ", " --help +Вывести справку и завершиться. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH УПРАВЛЕНИЕ ФЛАГАМИ PSW +Ассемблер принимает явные инструкции управления arithmetic/status flags: +.nf +SETF flag +CLRF flag +CLRCPUF +CLRMPUF +.fi +.PP +.B SETF +и +.B CLRF +принимают именованные флаги AF, CF, OF, SF, PF, ZF, RF, VF, DOMF, SNGF, +OVFF, UDFF, TLSF, PLSF, INDF и INXF, то есть PSW bits 0..15. HF, TF и IF +остаются допустимыми selectors для чтения через условные переходы, но этими +инструкциями не записываются; MODE является многобитовым полем и selector не +имеет. +.B CLRCPUF +очищает PSW[7:0], а +.B CLRMPUF +очищает PSW[15:8]. Обе инструкции сохраняют control/execution state выше bit 15. +.SH УПРАВЛЕНИЕ ИСХОДНОЙ ПОЗИЦИЕЙ +Ассемблер принимает директиву +.B #line +с десятичным номером строки и необязательным именем файла в кавычках. Она +предназначена для вывода препроцессора, меняет последующие позиции диагностик и +не записывается в MELF. +.SH СТАТУС ЗАВЕРШЕНИЯ +Ноль при успехе; ненулевое значение при синтаксической, семантической, I/O или +object-generation ошибке. +.SH ПРИМЕРЫ +.nf +mcpu-as -o main.o main.s +mcpu-as helper.s -o helper.o +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-ld (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-ld.1 b/man/ru/mcpu-ld.1 new file mode 100644 index 0000000..b57cb93 --- /dev/null +++ b/man/ru/mcpu-ld.1 @@ -0,0 +1,170 @@ +.TH MCPU-LD 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-ld \- компоновщик объектов, архивов и shared libraries MCPU +.SH СИНТАКСИС +.B mcpu-ld +.RI [ опции-компоновки ] " -o output input ..." +.SH ОПИСАНИЕ +.B mcpu-ld +компонует MELF ET_REL objects и статические библиотеки MCPU. Он создаёт partial +ET_REL, полностью статический ABI-v1 ET_EXEC, frozen ABI-v3 startup-dynamic +ET_EXEC или ABI-v3 ET_DYN shared object. +.PP +Без +.BR -r , +.B --shared +и +.B --dynamic +по умолчанию создаётся статический ABI-v1 ET_EXEC. +.SH РЕЖИМЫ ВЫХОДА +.TP +.B -r +Создать один partial-link MELF ET_REL. Неразрешённые strong symbols и RELA +могут остаться для последующей final link. STACK, HEAP, SONAME и shared objects +к этому режиму не относятся. +.TP +.B --shared +Создать frozen ABI-v3 MELF ET_DYN. Обязателен логический +.BR --soname . +Shared object может иметь только allocated .text/.data/.bss и не владеет +lifecycle, model spaces, STACK или HEAP. +.TP +.B --dynamic +Создать frozen ABI-v3 startup-dynamic MELF ET_EXEC. Normal dependencies +записываются как DT_NEEDED SONAME. +.SH AUTOMATIC HLL CRT +Для final static/dynamic ET_EXEC output mcpu-ld сначала анализирует явно +перечисленные входные ET_REL objects и только затем обрабатывает -l libraries. +Распознаются две compiler-normalized complete-program формы: +.nf +PROGRAM: strong GLOBAL/FUNC main + fini в одном ET_REL +MODEL: strong GLOBAL/FUNC init + life + fini в одном ET_REL +.fi +.PP +PROGRAM и MODEL roots взаимоисключающие. Incomplete или split MODEL definitions +отклоняются; PROGRAM main/fini также должны принадлежать одному defining ET_REL. +Смешивание HLL roots с низкоуровневым владением _start/_life является ошибкой. +Library members и shared-library definitions не участвуют в классификации +complete program. +.PP +После классификации mcpu-ld автоматически вставляет: +.nf +PROGRAM: crt0.o crti.o <explicit input objects> crtn.o +MODEL: crt1.o crti.o <explicit input objects> crtn.o +.fi +CRT objects загружаются из <MCPU-root>/lib/mcpu, где <MCPU-root> — активный +installation/system root. Отсутствующий или нечитаемый обязательный CRT object +является fatal error. Поиска fallback CRT в другом root и перехода HLL link в +low-level linking нет. +.PP +Low-level ET_EXEC inputs, самостоятельно владеющие _start/_life, компонуются без +HLL CRT insertion. Режимы -r и --shared не выполняют HLL complete-program +classification или CRT insertion. +.SH SYSTEM ROOT И CONFIGURATION +.TP +.B --no-config +Не читать mcpu-ld.conf; executable-derived runtime root и его default lib +остаются активными. +.TP +.BI --sys-root= ПУТЬ +Использовать ПУТЬ как MCPU system root, не читать configuration files и взять +ПУТЬ/lib как системный каталог библиотек. Относительный путь разрешается от +рабочего каталога запуска. +.TP +.BI --sys-root " ПУТЬ" +Эквивалентная двухаргументная форма. +.TP +.B --dump-config +Вывести effective MCPU configuration и завершиться. +.TP +.B --print-search-dirs +Вывести effective library search directories в порядке поиска и завершиться. +.SH БИБЛИОТЕКИ +.TP +.BI -L " каталог" +.TQ +.BI -L каталог +Добавить явный каталог поиска. -L каталоги идут раньше configured system path. +.TP +.BI -l " имя" +.TQ +.BI -l имя +Искать libИМЯ. ABI-v1 и -r рассматривают только libИМЯ.a; ABI-v3 dynamic/shared +в каждом каталоге сначала рассматривает libИМЯ.so, затем libИМЯ.a. +.TP +.B --static +Принудить все -l искать только libИМЯ.a, не меняя выбранный output mode. +Архивные members встраиваются статически и не создают DT_NEEDED. +.SH SHARED-LIBRARY IDENTITY +.TP +.BI --soname= ИМЯ +.TQ +.BI --soname " ИМЯ" +Установить обязательный SONAME ABI-v3 ET_DYN. Логическое ИМЯ не должно содержать '/'. +.SH ПАРАМЕТРЫ EXECUTABLE +.TP +.BI -o " файл" +Записать результат в файл. +.TP +.BI --stack-size= размер +Фиксированный размер STACK; положительный и кратный 16. Default 1048576. +.TP +.BI --heap-size= размер +Начальный HEAP. Default 1048576. +.TP +.BI --heap-limit= размер +Неизменяемый maximum HEAP. Default 1048576. +.TP +.B --no-heap +Не создавать PT_MCPU_HEAP. +.TP +.BR -v ", " --verbose +После успешной компоновки вывести краткую сводку о созданном MELF object. +По умолчанию успешный запуск ничего не выводит. +.TP +.BR -h ", " --help +Вывести справку. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.PP +Размер по умолчанию задаётся в байтах. Поддерживаются K/KiB, M/MiB, G/GiB; +K, M, G используют двоичные множители. +.SH АРХИВЫ И SYMBOL RESOLUTION +Статические библиотеки MCPU — обычные deterministic UNIX ar containers с MELF +ET_REL members. Member извлекается, когда он удовлетворяет текущему +неразрешённому strong GLOBAL symbol. Один unresolved WEAK сам по себе member не +вытягивает. +.SH ФАЙЛЫ +.TP +.I <runtime-root>/lib/mcpu/crt0.o, crt1.o, crti.o, crtn.o +Обязательные automatic HLL CRT objects для final PROGRAM/MODEL executable link. +.TP +.I <runtime-root>/etc/mcpu-ld.conf +Configuration из переносимого дерева MCPU. +.TP +.I /etc/mcpu/mcpu-ld.conf +Необязательный machine-wide override. +.TP +.I $HOME/.mcpu/etc/mcpu-ld.conf +Необязательный user override. +.SH СТАТУС ЗАВЕРШЕНИЯ +Ноль при успехе; ненулевое значение при ошибке опций, validation, symbol +resolution, relocation или записи результата. +.SH ПРИМЕРЫ +.nf +mcpu-ld --stack-size=64K --no-heap -o program main.o helper.o +mcpu-ld -r -o combined.o a.o b.o +mcpu-ld --shared --soname=libfoo.so.1 -o libfoo.so.1.0.0 foo.o +mcpu-ld --dynamic -L ./lib -lfoo -o program main.o +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-as (1), +.BR mcpu-ar (1), +.BR mcpu-run (1), +.BR mcpu-readelf (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-nm.1 b/man/ru/mcpu-nm.1 new file mode 100644 index 0000000..b58db53 --- /dev/null +++ b/man/ru/mcpu-nm.1 @@ -0,0 +1,59 @@ +.TH MCPU-NM 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-nm \- вывод символов MELF-файлов и архивов MCPU +.SH СИНТАКСИС +.B mcpu-nm +.RI [ опции ] " MELF-файл(ы)" +.SH ОПИСАНИЕ +.B mcpu-nm +показывает symbols из ET_REL, ET_EXEC, ET_DYN и deterministic MCPU archives. +По умолчанию читается .symtab и сортировка идёт по имени; для runtime symbols +используется +.BR -D . +.SH ОПЦИИ +.TP +.BR -a ", " --debug-syms +Все named symbols, включая FILE/SECTION. +.TP +.BR -g ", " --extern-only +Только external GLOBAL/WEAK. +.TP +.BR -u ", " --undefined-only +Только undefined symbols. +.TP +.BR -n ", " --numeric-sort +Сортировка по value. +.TP +.BR -r ", " --reverse-sort +Обратный выбранный порядок. +.TP +.BR -p ", " --no-sort +Порядок symbol table. +.TP +.BR -D ", " --dynamic +Использовать .dynsym вместо .symtab. +.TP +.BR -A ", " --print-file-name +Префиксовать каждую строку именем input; для archive указывается archive/member. +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH ПРИМЕРЫ +.nf +mcpu-nm file.o +mcpu-nm -n file.o +mcpu-nm -D libfoo.so.1 +mcpu-nm -A -D -g libfoo.so.1 +mcpu-nm -A libsimple.a +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-objcopy.1 b/man/ru/mcpu-objcopy.1 new file mode 100644 index 0000000..204aa9e --- /dev/null +++ b/man/ru/mcpu-objcopy.1 @@ -0,0 +1,71 @@ +.TH MCPU-OBJCOPY 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-objcopy \- безопасное копирование и редактирование metadata MELF +.SH СИНТАКСИС +.B mcpu-objcopy +.RI [ опции ] " INPUT OUTPUT" +.SH ОПИСАНИЕ +.B mcpu-objcopy +— MELF-to-MELF structural copier/editor. Это намеренно не ROM-image converter: +нет flat binary, Intel HEX, S-record, VMA/LMA, start-address или section-flags +conversion modes. +.PP +Editor защищает frozen MELF ABI v3: runtime-visible allocated sections, +dynamic-link metadata, runtime relocations и MCPU target/lifecycle data нельзя +удалять, переименовывать или изменять safe metadata interface. +.SH ОПЦИИ +.TP +.BI -j " ИМЯ" +.TQ +.BI --only-section= ИМЯ +Копировать только указанные sections; repeatable. Операция отклоняется, если +фильтрация выбрасывает runtime, dynamic или ET_REL link-critical data. +.TP +.BI -R " ИМЯ" +.TQ +.BI --remove-section= ИМЯ +Удалить safe non-runtime section. +.TP +.BI --dump-section= ИМЯ=ФАЙЛ +Записать contents section в ФАЙЛ. Для archive input отклоняется как +неоднозначная операция. +.TP +.BI --add-section= ИМЯ=ФАЙЛ +Добавить non-ALLOC SHT_PROGBITS metadata из ФАЙЛ. Reserved runtime names +запрещены. +.TP +.BI --update-section= ИМЯ=ФАЙЛ +Заменить contents safe non-ALLOC SHT_PROGBITS section. +.TP +.BI --rename-section= СТАРОЕ=НОВОЕ +Переименовать safe non-ALLOC section; reserved runtime names защищены. +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH STRUCTURAL INVARIANTS +Metadata-only edits перестраивают .shstrtab и remap section references, +включая st_shndx сохраняемых symbols, но не изменяют allocated image и program +headers. Non-ALLOC metadata tail может быть repacked. +.SH АРХИВЫ +Deterministic MCPU archives преобразуются member-by-member с сохранением +archive symbol-index semantics. +.SH ПРИМЕРЫ +.nf +mcpu-objcopy input.o output.o +mcpu-objcopy --add-section=.note.build=build.txt input.o output.o +mcpu-objcopy --update-section=.note.build=new.txt input.o output.o +mcpu-objcopy --rename-section=.note.build=.note.release input.o output.o +mcpu-objcopy -R .note.release input.o output.o +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-strip (1), +.BR mcpu-objdump (1), +.BR mcpu-readelf (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-objdump.1 b/man/ru/mcpu-objdump.1 new file mode 100644 index 0000000..ec2dcd4 --- /dev/null +++ b/man/ru/mcpu-objdump.1 @@ -0,0 +1,66 @@ +.TH MCPU-OBJDUMP 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-objdump \- секции, relocations и дизассемблирование инструкций MCPU +.SH СИНТАКСИС +.B mcpu-objdump +.RI [ опции ] " MELF-файл(ы)" +.SH ОПИСАНИЕ +.B mcpu-objdump +просматривает MELF objects, executables, shared objects и deterministic +archives. Дизассемблер использует тот же LibMCPU instruction decoder, что и +runtime, а symbol/relocation information применяется для аннотаций без +подмены raw operand bytes. +.SH ОПЦИИ +.TP +.BR -d ", " --disassemble +Дизассемблировать executable PROGBITS sections. +.TP +.BR -D ", " --disassemble-all +Пытаться дизассемблировать все выбранные nonempty PROGBITS. +.TP +.BR -r ", " --reloc +Показать relocations; при disassembly relocation привязывается к инструкции, +содержащей поле relocation. +.TP +.BR -s ", " --full-contents +Raw contents sections. +.TP +.BR -t ", " --syms +.symtab symbols. +.TP +.BR -T ", " --dynamic-syms +.dynsym symbols. +.TP +.BR -h ", " --section-headers +Section headers. +.TP +.BI -j " ИМЯ" +.TQ +.BI --section= ИМЯ +Ограничить section-oriented output; repeatable. +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.PP +Нужна хотя бы одна display option. Нераспознаваемые bytes печатаются как +.byte, после чего просмотр продолжается. +.SH ПРИМЕРЫ +.nf +mcpu-objdump -h file.o +mcpu-objdump -t -r -d file.o +mcpu-objdump -T -d -r libfoo.so.1 +mcpu-objdump -s -j .data file.o +mcpu-objdump -d libsimple.a +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-readelf (1), +.BR mcpu-nm (1), +.BR mcpu-objcopy (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-readelf.1 b/man/ru/mcpu-readelf.1 new file mode 100644 index 0000000..2c6d1ae --- /dev/null +++ b/man/ru/mcpu-readelf.1 @@ -0,0 +1,76 @@ +.TH MCPU-READELF 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-readelf \- структурный просмотр MELF-файлов MCPU +.SH СИНТАКСИС +.B mcpu-readelf +.RI "опции MELF-файл(ы)" +.SH ОПИСАНИЕ +.B mcpu-readelf +— read-only inspector для MELF ABI v1/v2/v3. Показывает headers, sections, +symbols, RELA, dynamic dependency metadata и MCPU target/lifecycle records, не +изменяя входные файлы. +.SH ОПЦИИ +.TP +.BR -a ", " --all +Эквивалент -h -l -S -s -r -d -A. +.TP +.BR -e ", " --headers +Эквивалент -h -l -S. +.TP +.BR -h ", " --file-header +Показать MELF file header. +.TP +.BR -l ", " --program-headers +Показать program headers. +.TP +.B --segments +Alias для --program-headers. +.TP +.BR -S ", " --section-headers +Показать section headers. +.TP +.B --sections +Alias для --section-headers. +.TP +.BR -s ", " --symbols +Показать .symtab/.dynsym. +.TP +.B --syms +Alias для --symbols. +.TP +.BR -r ", " --relocs +Показать RELA sections. +.TP +.BR -d ", " --dynamic +Показать ABI-v3 .dynamic, включая SONAME/NEEDED. +.TP +.BR -A ", " --arch-specific +Показать MCPU target/lifecycle metadata. +.TP +.BR -W ", " --wide +Не усекать вывод; compatibility option. +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH СТАТУС ЗАВЕРШЕНИЯ +Ноль при успешном чтении всех файлов; ненулевое значение при malformed, +unsupported или недоступном input. +.SH ПРИМЕРЫ +.nf +mcpu-readelf -h -S file.o +mcpu-readelf -s -r file.o +mcpu-readelf -l -A program +mcpu-readelf -d libfoo.so.1 +mcpu-readelf -a program +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-nm (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-run.1 b/man/ru/mcpu-run.1 new file mode 100644 index 0000000..9701dcf --- /dev/null +++ b/man/ru/mcpu-run.1 @@ -0,0 +1,199 @@ +.TH MCPU-RUN 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-run \- загрузчик, runtime и supervisor исполнения MCPU +.SH СИНТАКСИС +.B mcpu-run +.RI [ runtime-options ] " -- executable " [ program-args ...] +.br +.B mcpu-run +.RI [ runtime-options ] " executable " [ program-args ...] +.SH ОПИСАНИЕ +.B mcpu-run +— loader, startup linker, execution supervisor и host-side test bench для MELF +ET_EXEC. Предпочтительна форма с явным разделителем +.BR -- . +Размеры STACK и HEAP являются частью executable ABI и не переопределяются +runner-ом. +.PP +Поддерживаются статический ABI v1, исторический explicit-preload ABI v2 и +frozen ABI v3. Discovery зависимостей, symbol selection и runtime relocation +завершаются до первой guest instruction. +.SH HLL STARTUP И LIFECYCLE +HLL PROGRAM или MODEL executable всё равно входит через обычный MELF +_start/_life/_fini lifecycle, предоставленный linked CRT. mcpu-run не вызывает +main, init, life или fini напрямую. +.PP +Перед _start loader один раз создаёт в STACK постоянную область McpuStartup v1 +с version, size, argc, PTR64 argv и reserved zero field. Массив argv и строки +аргументов остаются действительными весь executable lifetime. HLL CRT проверяет +это состояние и переводит его в обычный CALL ABI. +.PP +PROGRAM выполняет HLL main один раз и завершает работу с возвращённым int32 +status. MODEL выполняет INIT через init(), затем вызывает life() один раз на +каждой lifecycle iteration. INT32_MIN означает продолжение через одну LIFE END +boundary; zero — нормальное завершение; positive value — error exit status. +Остальные отрицательные результаты life зарезервированы HLL Runtime ABI v1. +.PP +Graceful HLL exit вызывает user fini(), __mcpu_fini_array() и +__mcpu_libc_fini() перед финальным FINI END. Startup failure до перехода CRT в +RUNNING пропускает эти finalizers. Diagnostic stop по --steps остаётся +supervisor stop и не синтезирует graceful HLL finalization. +.SH SYSTEM OPTIONS +.TP +.B --no-config +Не читать mcpu-ld.conf, сохраняя executable-derived default system library tree. +.TP +.BI --sys-root= ПУТЬ +Использовать ПУТЬ как MCPU root, не читать config и использовать ПУТЬ/lib. +.TP +.B --dump-config +Вывести effective configuration и завершиться. +.TP +.B --print-search-dirs +Вывести search directories и завершиться. +.SH LIBRARY OPTIONS +Эти опции должны находиться перед executable или явным --. +.TP +.BI --library-path= КАТАЛОГ[:КАТАЛОГ...] +Добавить явные каталоги поиска. Опция повторяемая; эти каталоги предшествуют +system library path. +.TP +.BI --ldpreload= a.so:b.so +Предзагрузить ABI-v2/ABI-v3 shared objects. Имя без '/' ищется по search path; +имя с '/' считается явным pathname. +.SH ОКРУЖЕНИЕ +.TP +.B MCPU_LD_PRELOAD +Colon-separated preload list, применяемый раньше command-line --ldpreload. При +равной силе определения действует rightmost priority, поэтому command-line +preload может переопределить environment preload. +.SH INSPECTION ПОСЛЕ ОСТАНОВА +.TP +.BI --int-reg= r<bits>@<addr> +Вывести signed integer register. +.TP +.BI --real-reg= r<bits>@<addr> +Вывести real register. +.TP +.BI --int-data= <bits>@<addr> +.TQ +.BI --real-data= <bits>@<addr> +.TQ +.BI --raw-data= <bytes>@<addr> +Inspection DATA. +.TP +.BI --int-control= <bits>@<addr> +.TQ +.BI --real-control= <bits>@<addr> +.TQ +.BI --raw-control= <bytes>@<addr> +Inspection CONTROL. +.TP +.BI --int-heap= <bits>@<addr> +.TQ +.BI --real-heap= <bits>@<addr> +.TQ +.BI --raw-heap= <bytes>@<addr> +Inspection HEAP. +.TP +.BI --int-input= <bits>@<addr> +.TQ +.BI --real-input= <bits>@<addr> +.TQ +.BI --raw-input= <bytes>@<addr> +Inspection INPUT. +.TP +.BI --int-output= <bits>@<addr> +.TQ +.BI --real-output= <bits>@<addr> +.TQ +.BI --raw-output= <bytes>@<addr> +Inspection OUTPUT. +.TP +.BI --int-trace= <bits>@<addr> +.TQ +.BI --real-trace= <bits>@<addr> +.TQ +.BI --raw-trace= <bytes>@<addr> +Inspection TRACE. +.SH LIFECYCLE CONTROL +.TP +.BI --digits= n +Mantissa digits для real output; default 80. +.TP +.B --init-only +Выполнить INIT до END и остановиться. +.TP +.BI --steps= n +Остановиться после n LIFE END boundaries. +.TP +.BR -h ", " --help +Вывести справку. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH STAGE ACTIONS +Повторяемые stage options выполняют host actions на lifecycle boundaries: +.TP +.BI --before-run= action +После load/startup-link/relocations/startup area, до первой guest instruction. +.TP +.BI --after-init= action +После INIT END или один раз на logical empty INIT boundary без .init. +.TP +.BI --on-step= action +После каждого LIFE END. +.TP +.BI --after-run= action +После final normal stop; graceful FINI уже завершён. +.PP +Формы actions: +.nf +print-int-regs:r<bits>@<addr>[,...] +print-real-regs:r<bits>@<addr>[,...] +set-int-regs:r<bits>@<addr>=<value>[,...] +set-real-regs:r<bits>@<addr>=<value>[,...] +print-int-<memory>:<bits>@<addr>[,...] +print-real-<memory>:<bits>@<addr>[,...] +set-int-<memory>:<bits>@<addr>=<value>[,...] +set-real-<memory>:<bits>@<addr>=<value>[,...] +print-raw-<memory>:<bytes>@<addr> +.fi +.PP +memory = data, control, heap, input, output или trace. Scalar print выводит +comma-list одной строкой. Raw action содержит ровно один dump; для нескольких +нужно повторить stage option. set-raw-* отсутствуют. Set values разбираются +LibMPUIO mpu_sscanf(). Step-number filters не реализованы. +.SH LIFECYCLE И EXIT +END возвращает управление supervisor-у на INIT/LIFE/FINI boundaries. Graceful +EXIT может запустить optional FINI. Diagnostic stop по --steps не синтезирует +EXIT и сам по себе FINI не запускает. +.SH ФАЙЛЫ +.TP +.I <runtime-root>/etc/mcpu-ld.conf +Installed relocatable-tree configuration. +.TP +.I /etc/mcpu/mcpu-ld.conf +Machine-wide override. +.TP +.I $HOME/.mcpu/etc/mcpu-ld.conf +User override. +.SH СТАТУС ЗАВЕРШЕНИЯ +При нормальном завершении возвращается guest exit status. Loader/startup-linker, +option и runtime errors дают ненулевой host-side status. +.SH ПРИМЕРЫ +.nf +mcpu-run -- program arg1 arg2 +mcpu-run --library-path=./lib -- program +MCPU_LD_PRELOAD=libhook.so mcpu-run -- program +mcpu-run --steps=20 --on-step=print-real-trace:128@0,128@16 -- pendulum +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-ld (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/man/ru/mcpu-strip.1 b/man/ru/mcpu-strip.1 new file mode 100644 index 0000000..3e437ff --- /dev/null +++ b/man/ru/mcpu-strip.1 @@ -0,0 +1,53 @@ +.TH MCPU-STRIP 1 "Октябрь 2026" "LibMCPU" "Команды пользователя" + +.SH ИМЯ +mcpu-strip \- безопасное удаление non-runtime symbol metadata из MELF +.SH СИНТАКСИС +.B mcpu-strip +.RI [ опции ] " MELF-файл" +.SH ОПИСАНИЕ +.B mcpu-strip +намеренно консервативен. Он удаляет ordinary non-ALLOC SHT_SYMTAB из +ET_EXEC/ET_DYN только когда ни одна сохраняемая section от него не зависит, и +удаляет связанную ordinary string table только когда она становится +unreferenced. +.PP +ET_REL symbol tables сохраняются как часть последующей link semantics. +.dynsym/.dynstr/.dynamic, RELA, MCPU target/lifecycle metadata, allocated/model +sections и program headers всегда сохраняются. Современные final-image writers +LibMCPU уже компактны, поэтому нормальный ET_EXEC/ET_DYN часто остаётся +byte-for-byte неизменным. +.SH ОПЦИИ +.TP +.BI -o " файл" +.TQ +.BI --output= ФАЙЛ +Записать отдельный результат вместо in-place replacement. +.TP +.B --help +Справка. +.TP +.B --version +Вывести имя программы, идентификатор инструментария MCPU и версию, затем завершить работу. +.TP +.B --dump-version +Вывести только номер версии пакета MCPU и завершить работу. +.SH IN-PLACE SAFETY +In-place replacement выполняется через temporary file в том же каталоге и +rename с сохранением mode исходного файла. Allocated image и program headers не +перепаковываются safe metadata policy. +.SH АРХИВЫ +MCPU archives обрабатываются member-by-member; link-critical ET_REL symbols +сохраняются. +.SH ПРИМЕРЫ +.nf +mcpu-strip program +mcpu-strip -o program.stripped program +mcpu-strip libfoo.so.1 +mcpu-strip libsimple.a +.fi +.SH СМ. ТАКЖЕ +.BR mcpu-objcopy (1), +.BR mcpu-readelf (1), +.BR mcpu-objdump (1), +.BR libmcpu (7) diff --git a/mcpu-config.in b/mcpu-config.in new file mode 100644 index 0000000..8644eb3 --- /dev/null +++ b/mcpu-config.in @@ -0,0 +1,91 @@ +#!/bin/sh + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +VERSION=@VERSION@ + +usage() +{ +cat <<EOF + + Usage : mcpu-config [OPTIONS] + Options : + [--prefix] + [--exec-prefix] + [--version] + [--cppflags] + [--cflags] + [--libs] + [--ldflags] + +EOF + exit $1 +} + +if test $# -eq 0; then + usage 1 1>&2 +fi + +while test $# -gt 0; do + case $1 in + --prefix) + echo_prefix=yes + ;; + --exec-prefix) + echo_exec_prefix=yes + ;; + --version) + echo ${VERSION} + ;; + --cppflags) + echo_cppflags=yes + ;; + --cflags) + echo_cflags=yes + ;; + --libs) + echo_libs=yes + ;; + --ldflags) + echo_ldflags=yes + ;; + *) + usage 1 1>&2 + ;; + esac + shift +done + + +libmcpu_cppflags=" -I${includedir}" +libmcpu_cflags=" -I${includedir}" +libmcpu_ldflags=" -L${libdir}" +libmcpu_libs=" -lmcpu" + + +if test "x${echo_prefix}" = "xyes"; then + echo ${prefix} +fi + +if test "x${echo_exec_prefix}" = "xyes"; then + echo ${exec_prefix} +fi + +if test "x${echo_cppflags}" = "xyes"; then + echo ${libmcpu_cppflags} +fi + +if test "x${echo_cflags}" = "xyes"; then + echo ${libmcpu_cflags} +fi + +if test "x${echo_ldflags}" = "xyes"; then + echo ${libmcpu_ldflags} +fi + +if test "x${echo_libs}" = "xyes"; then + echo ${libmcpu_libs} +fi diff --git a/mcpu.pc.in b/mcpu.pc.in new file mode 100644 index 0000000..eff47f6 --- /dev/null +++ b/mcpu.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: mcpu +Description: Virtual MCPU library based on LibMPU +Version: @PACKAGE_VERSION@ +Requires: mpu >= @LIBMPU_VERSION@ +Libs: -L${libdir} -lmcpu +Cflags: -I${includedir} diff --git a/sysroot/Makefile.am b/sysroot/Makefile.am new file mode 100644 index 0000000..6285067 --- /dev/null +++ b/sysroot/Makefile.am @@ -0,0 +1,41 @@ + +mcpuconfdir = @MCPU_ETCDIR@ + +dist_mcpuconf_DATA = etc/mcpu-ld.conf + +crt_srcdir = $(srcdir)/usr/src/mcpu/crt +crt_builddir = $(builddir)/lib/mcpu + +crt_objects = $(crt_builddir)/crt0.o \ + $(crt_builddir)/crt1.o \ + $(crt_builddir)/crti.o \ + $(crt_builddir)/crtn.o + +mcpucrtobjectsdir = @MCPU_ROOT@/lib/mcpu +nodist_mcpucrtobjects_DATA = $(crt_objects) + +all-local: $(crt_objects) + +$(crt_builddir)/crt0.o: $(crt_srcdir)/crt0.S $(top_builddir)/utils/mcpu-as + $(MKDIR_P) $(crt_builddir) + $(top_builddir)/utils/mcpu-as -o $@ $(crt_srcdir)/crt0.S + +$(crt_builddir)/crt1.o: $(crt_srcdir)/crt1.S $(top_builddir)/utils/mcpu-as + $(MKDIR_P) $(crt_builddir) + $(top_builddir)/utils/mcpu-as -o $@ $(crt_srcdir)/crt1.S + +$(crt_builddir)/crti.o: $(crt_srcdir)/crti.S $(top_builddir)/utils/mcpu-as + $(MKDIR_P) $(crt_builddir) + $(top_builddir)/utils/mcpu-as -o $@ $(crt_srcdir)/crti.S + +$(crt_builddir)/crtn.o: $(crt_srcdir)/crtn.S $(top_builddir)/utils/mcpu-as + $(MKDIR_P) $(crt_builddir) + $(top_builddir)/utils/mcpu-as -o $@ $(crt_srcdir)/crtn.S + +EXTRA_DIST = README \ + usr/src/mcpu/crt/crt0.S \ + usr/src/mcpu/crt/crt1.S \ + usr/src/mcpu/crt/crti.S \ + usr/src/mcpu/crt/crtn.S + +CLEANFILES = $(crt_objects) diff --git a/sysroot/README b/sysroot/README new file mode 100644 index 0000000..93401f8 --- /dev/null +++ b/sysroot/README @@ -0,0 +1,52 @@ + +MCPU sysroot source tree +======================== + +This directory is the canonical build-time source image of the target MCPU +system root. It contains target-facing ABI/configuration material and CRT +sources; it does not contain manual examples or regression-only emulators. + +Runtime-root configuration +-------------------------- + +The canonical LibMCPU runtime-root configuration source is: + + sysroot/etc/mcpu-ld.conf + +`make install` installs it as: + + <MCPU-root>/etc/mcpu-ld.conf + +where `<MCPU-root>` is the configured target MCPU root (normally +`$(libdir)/mcpu`). The installed runtime path is unchanged; only source-tree +ownership now follows the sysroot layout. + +Assembler ABI headers +--------------------- + +The canonical public assembler ABI headers are: + + sysroot/include/sys/asm/ + +They are the only source used by the LibMCPU build to install +`<MCPU-root>/include/sys/asm/` and by regression tests that validate the public +assembler ABI. + +HLL CRT +------- + +The canonical LibMCPU CRT assembler sources are: + + sysroot/usr/src/mcpu/crt/ + +They are assembled out-of-tree into: + + $builddir/sysroot/lib/mcpu/ + +and `make install` installs `crt0.o`, `crt1.o`, `crti.o`, and `crtn.o` under: + + <MCPU-root>/lib/mcpu/ + +The target sysroot intentionally contains no `libc-stub` or `hll-emul` +development trees. HLL/CRT regression fixtures belong to `tests/fixtures/`; +real MCPU Libc and compiler-output examples are separate projects/packages. diff --git a/sysroot/etc/mcpu-ld.conf b/sysroot/etc/mcpu-ld.conf new file mode 100644 index 0000000..cd3ecbe --- /dev/null +++ b/sysroot/etc/mcpu-ld.conf @@ -0,0 +1,18 @@ +/* + * LibMCPU linker/runtime configuration. + * + * Effective root of the MCPU system library tree. + * + * The default is not stored as an absolute installation path in this file. + * At run time mcpu-ld and mcpu-run locate their real executable, take the + * parent of its executable directory as the MCPU installation root, and use + * <runtime-root>/lib. A higher-priority system or per-user configuration may + * replace that path completely by defining MCPU_LD_SYSTEM_LIBRARY_PATH. + * Set an empty value in an override configuration to disable the configured + * system library tree. + * + * Example override: + * MCPU_LD_SYSTEM_LIBRARY_PATH = $HOME/mcpu-next/lib; + */ + +/* MCPU_LD_SYSTEM_LIBRARY_PATH = $HOME/mcpu-next/lib; */ diff --git a/sysroot/include/sys/asm/dirent.h b/sysroot/include/sys/asm/dirent.h new file mode 100644 index 0000000..d761bf6 --- /dev/null +++ b/sysroot/include/sys/asm/dirent.h @@ -0,0 +1,23 @@ + +#ifndef __MCPU_SYS_ASM_DIRENT_H +#define __MCPU_SYS_ASM_DIRENT_H + +/* Fixed external MCPU assembler ABI corresponding to struct __cpu_dirent. */ +#define __MCPU_DIRENT_SIZE 288 +#define __MCPU_DIRENT_D_INO 0 +#define __MCPU_DIRENT_D_OFF 8 +#define __MCPU_DIRENT_D_RECLEN 16 +#define __MCPU_DIRENT_D_TYPE 24 +#define __MCPU_DIRENT_D_NAME 32 +#define __MCPU_DIRENT_NAME_SIZE 256 + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 + +#endif /* __MCPU_SYS_ASM_DIRENT_H */ diff --git a/sysroot/include/sys/asm/errno.h b/sysroot/include/sys/asm/errno.h new file mode 100644 index 0000000..f018923 --- /dev/null +++ b/sysroot/include/sys/asm/errno.h @@ -0,0 +1,155 @@ + +#ifndef __MCPU_SYS_ASM_ERRNO_H +#define __MCPU_SYS_ASM_ERRNO_H + +/* + * Stable MCPU system-call error numbers. + * + * These are MCPU ABI values. They are not HOST errno numbers even when + * the numeric values happen to coincide on a particular HOST system. + */ + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain */ +#define ERANGE 34 /* Math result not representable */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ + +#define EWOULDBLOCK EAGAIN /* Operation would block */ + +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ + +#define EDEADLOCK EDEADLK /* File locking deadlock */ + +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Cannot access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted syscall should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ +#define EFTYPE 134 /* Wrong file type for intended operation */ + +#define ENOTSUP EOPNOTSUPP /* Operation not supported */ + +#define EUNKNOWN 4095 /* Unknown or unmapped HOST error */ + +#endif /* __MCPU_SYS_ASM_ERRNO_H */ diff --git a/sysroot/include/sys/asm/fcntl.h b/sysroot/include/sys/asm/fcntl.h new file mode 100644 index 0000000..921c3b7 --- /dev/null +++ b/sysroot/include/sys/asm/fcntl.h @@ -0,0 +1,50 @@ + +#ifndef __MCPU_SYS_ASM_FCNTL_H +#define __MCPU_SYS_ASM_FCNTL_H + +/* MCPU open(2) ABI flags. They are guest values, not HOST O_* values. */ +#define O_ACCMODE 00000003 +#define O_RDONLY 00000000 +#define O_WRONLY 00000001 +#define O_RDWR 00000002 +#define O_CREAT 00000100 +#define O_EXCL 00000200 +#define O_NOCTTY 00000400 +#define O_TRUNC 00001000 +#define O_APPEND 00002000 +#define O_NONBLOCK 00004000 + +/* MCPU pipe2(2) descriptor-creation flag. */ +#define O_CLOEXEC 02000000 + +/* MCPU fcntl(2) commands. These are guest ABI values. */ +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + +/* Guest descriptor flags. */ +#define FD_CLOEXEC 1 + +/* MCPU permission/mode bits used by open/creat/mkdir/chmod/fchmod. */ +#define S_ISUID 00004000 +#define S_ISGID 00002000 +#define S_ISVTX 00001000 + +#define S_IRWXU 00000700 +#define S_IRUSR 00000400 +#define S_IWUSR 00000200 +#define S_IXUSR 00000100 + +#define S_IRWXG 00000070 +#define S_IRGRP 00000040 +#define S_IWGRP 00000020 +#define S_IXGRP 00000010 + +#define S_IRWXO 00000007 +#define S_IROTH 00000004 +#define S_IWOTH 00000002 +#define S_IXOTH 00000001 + +#endif /* __MCPU_SYS_ASM_FCNTL_H */ diff --git a/sysroot/include/sys/asm/mpu-errno.h b/sysroot/include/sys/asm/mpu-errno.h new file mode 100644 index 0000000..3f42751 --- /dev/null +++ b/sysroot/include/sys/asm/mpu-errno.h @@ -0,0 +1,42 @@ + +#ifndef __MCPU_SYS_ASM_MPU_ERRNO_H +#define __MCPU_SYS_ASM_MPU_ERRNO_H + +/* + * MPU -- Math Processor Unit error numbers. + * + * These values describe the error-state registers of the MCPU mathematical + * coprocessor and intentionally preserve the LibMPU 1.0.25 names and values. + */ +#define __EUNKNOWN__ 0 /* Unknown error type */ + +/* Integer error types. */ +#define __I_ESIZE__ 1 /* Invalid size of operand(s) */ +#define __I_ESHIFT__ 2 /* Invalid number of shifts */ +#define __I_ENUMBER__ 3 /* Invalid number */ +#define __I_ERADIX__ 4 /* Invalid radix */ +#define __I_MAX_ERRNO 5 + +/* Real error types. */ +#define __R_ESIZE__ 1 /* Invalid size of operand(s) */ +#define __R_ETRUNC__ 2 /* Invalid number of TRUNC bits */ +#define __R_MAX_ERRNO 3 + +/* Complex error types. */ +#define __C_EDIVZEROBYZERO__ 1 /* Complex division by zero */ +#define __C_ELOGOFZERO__ 2 /* LOG of zero magnitude */ +#define __C_EPOWOFZERO__ 3 /* POW of zero magnitude */ +#define __C_MAX_ERRNO 4 + +/* Math error types. */ +#define __DOMAIN__ 1 /* Argument domain error */ +#define __SING__ 2 /* Argument singularity */ +#define __OVERFLOW__ 3 /* Overflow range error */ +#define __UNDERFLOW__ 4 /* Underflow range error */ +#define __TLOSS__ 5 /* Total loss of precision */ +#define __PLOSS__ 6 /* Partial loss of precision */ +#define __INVALID__ 7 /* Invalid operation */ +#define __INEXACT__ 8 /* Inexact result */ +#define __M_MAX_ERRNO 9 + +#endif /* __MCPU_SYS_ASM_MPU_ERRNO_H */ diff --git a/sysroot/include/sys/asm/select.h b/sysroot/include/sys/asm/select.h new file mode 100644 index 0000000..2e3c8d1 --- /dev/null +++ b/sysroot/include/sys/asm/select.h @@ -0,0 +1,18 @@ + +#ifndef __MCPU_SYS_ASM_SELECT_H +#define __MCPU_SYS_ASM_SELECT_H + +#include <sys/asm/time.h> + +/* MCPU select(2) descriptor-set ABI. The set covers all 256 guest fd values. */ +#define __MCPU_FD_SETSIZE 256 +#define __MCPU_FD_SET_WORDS 4 +#define __MCPU_FD_SET_SIZE 32 + +/* A set is four target-order u64 words. Bit n selects guest fd n. */ +#define __MCPU_FD_SET_WORD0 0 +#define __MCPU_FD_SET_WORD1 8 +#define __MCPU_FD_SET_WORD2 16 +#define __MCPU_FD_SET_WORD3 24 + +#endif /* __MCPU_SYS_ASM_SELECT_H */ diff --git a/sysroot/include/sys/asm/stat.h b/sysroot/include/sys/asm/stat.h new file mode 100644 index 0000000..dd83d94 --- /dev/null +++ b/sysroot/include/sys/asm/stat.h @@ -0,0 +1,34 @@ + +#ifndef __MCPU_SYS_ASM_STAT_H +#define __MCPU_SYS_ASM_STAT_H + +/* Fixed external MCPU assembler ABI layout corresponding to struct __cpu_stat. */ +#define __MCPU_STAT_SIZE 128 +#define __MCPU_STAT_ST_DEV 0 +#define __MCPU_STAT_ST_INO 8 +#define __MCPU_STAT_ST_MODE 16 +#define __MCPU_STAT_ST_NLINK 24 +#define __MCPU_STAT_ST_UID 32 +#define __MCPU_STAT_ST_GID 40 +#define __MCPU_STAT_ST_RDEV 48 +#define __MCPU_STAT_ST_SIZE 56 +#define __MCPU_STAT_ST_BLKSIZE 64 +#define __MCPU_STAT_ST_BLOCKS 72 +#define __MCPU_STAT_ST_ATIME_SEC 80 +#define __MCPU_STAT_ST_ATIME_NSEC 88 +#define __MCPU_STAT_ST_MTIME_SEC 96 +#define __MCPU_STAT_ST_MTIME_NSEC 104 +#define __MCPU_STAT_ST_CTIME_SEC 112 +#define __MCPU_STAT_ST_CTIME_NSEC 120 + +/* MCPU st_mode file-type bits. */ +#define S_IFMT 000170000 +#define S_IFIFO 000010000 +#define S_IFCHR 000020000 +#define S_IFDIR 000040000 +#define S_IFBLK 000060000 +#define S_IFREG 000100000 +#define S_IFLNK 000120000 +#define S_IFSOCK 000140000 + +#endif /* __MCPU_SYS_ASM_STAT_H */ diff --git a/sysroot/include/sys/asm/syscall.h b/sysroot/include/sys/asm/syscall.h new file mode 100644 index 0000000..9d5d630 --- /dev/null +++ b/sysroot/include/sys/asm/syscall.h @@ -0,0 +1,47 @@ + +#ifndef __MCPU_SYS_ASM_SYSCALL_H +#define __MCPU_SYS_ASM_SYSCALL_H + +/* MCPU system call numbers. */ +#define __NR_brk 10 +#define __NR_gettimeofday 0x01c +#define __NR_clock_getres 0x021 +#define __NR_clock_gettime 0x022 +#define __NR_nanosleep 0x030 +#define __NR_clock_nanosleep 0x031 + +#define __NR_read 0x100 +#define __NR_write 0x101 +#define __NR_lseek 0x102 +#define __NR_open 0x103 +#define __NR_creat 0x104 +#define __NR_close 0x105 +#define __NR_link 0x106 +#define __NR_unlink 0x107 +#define __NR_chdir 0x108 +#define __NR_mkdir 0x109 +#define __NR_chmod 0x10b +#define __NR_chown 0x10c +#define __NR_access 0x10f +#define __NR_rename 0x111 +#define __NR_rmdir 0x112 +#define __NR_symlink 0x113 +#define __NR_readlink 0x114 + +#define __NR_stat 0x115 +#define __NR_fstat 0x116 +#define __NR_lstat 0x117 +#define __NR_fcntl 0x119 +#define __NR_pipe2 0x11a +#define __NR_getdents 0x11d +#define __NR_select 0x11e +#define __NR_fsync 0x120 +#define __NR_truncate 0x121 +#define __NR_ftruncate 0x122 +#define __NR_fchmod 0x123 +#define __NR_fchown 0x124 +#define __NR_fchdir 0x125 +#define __NR_dup 0x126 +#define __NR_dup2 0x127 + +#endif /* __MCPU_SYS_ASM_SYSCALL_H */ diff --git a/sysroot/include/sys/asm/time.h b/sysroot/include/sys/asm/time.h new file mode 100644 index 0000000..38932d5 --- /dev/null +++ b/sysroot/include/sys/asm/time.h @@ -0,0 +1,22 @@ + +#ifndef __MCPU_SYS_ASM_TIME_H +#define __MCPU_SYS_ASM_TIME_H + +/* HOST-independent MCPU clock IDs. */ +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +/* MCPU clock_nanosleep(2) flag. */ +#define TIMER_ABSTIME 1 + +/* Fixed MCPU timeval ABI: two signed 64-bit fields. */ +#define __MCPU_TIMEVAL_SIZE 16 +#define __MCPU_TIMEVAL_TV_SEC 0 +#define __MCPU_TIMEVAL_TV_USEC 8 + +/* Fixed MCPU timespec ABI: two signed 64-bit fields. */ +#define __MCPU_TIMESPEC_SIZE 16 +#define __MCPU_TIMESPEC_TV_SEC 0 +#define __MCPU_TIMESPEC_TV_NSEC 8 + +#endif /* __MCPU_SYS_ASM_TIME_H */ diff --git a/sysroot/include/sys/asm/types.h b/sysroot/include/sys/asm/types.h new file mode 100644 index 0000000..7866f0f --- /dev/null +++ b/sysroot/include/sys/asm/types.h @@ -0,0 +1,16 @@ + +#ifndef __MCPU_SYS_ASM_TYPES_H +#define __MCPU_SYS_ASM_TYPES_H + +/* Scalar widths of the descriptor-I/O syscall ABI. */ +#define __MCPU_FD_BITS 32 +#define __MCPU_SIZE_BITS 64 +#define __MCPU_SSIZE_BITS 64 +#define __MCPU_OFF_BITS 64 +#define __MCPU_MODE_BITS 32 +#define __MCPU_UID_BITS 64 +#define __MCPU_GID_BITS 64 +#define __MCPU_ID_NO_CHANGE -1 +#define __MCPU_PTR_BITS 64 + +#endif /* __MCPU_SYS_ASM_TYPES_H */ diff --git a/sysroot/include/sys/asm/unistd.h b/sysroot/include/sys/asm/unistd.h new file mode 100644 index 0000000..4ff629c --- /dev/null +++ b/sysroot/include/sys/asm/unistd.h @@ -0,0 +1,23 @@ + +#ifndef __MCPU_SYS_ASM_UNISTD_H +#define __MCPU_SYS_ASM_UNISTD_H + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +/* pipe2(2) writes two u64 guest descriptors to this fixed output array. */ +#define __MCPU_PIPEFD_SIZE 16 +#define __MCPU_PIPEFD_READ 0 +#define __MCPU_PIPEFD_WRITE 8 + +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#endif /* __MCPU_SYS_ASM_UNISTD_H */ diff --git a/sysroot/usr/src/mcpu/crt/crt0.S b/sysroot/usr/src/mcpu/crt/crt0.S new file mode 100644 index 0000000..47a0bc0 --- /dev/null +++ b/sysroot/usr/src/mcpu/crt/crt0.S @@ -0,0 +1,22 @@ +; MCPU HLL PROGRAM adapter. + +.global __mcpu_crt_startup +.global main + +.text +__mcpu_crt_hll_program: +_start_life: + CALL __mcpu_crt_startup + + ; Persistent McpuStartup is again at top-level SP after RET. + LDSP r64@0x7fe00, 8 + LDSP r64@0x7fe08, 16 + + ; main(int64 argc, PTR64 argv) + ADJSP -16 + STSP 0, r64@0x7fe00 + STSP 8, r64@0x7fe08 + CALL main + ADJSP 16 + + EXIT r32@0x7fffc diff --git a/sysroot/usr/src/mcpu/crt/crt1.S b/sysroot/usr/src/mcpu/crt/crt1.S new file mode 100644 index 0000000..34880e1 --- /dev/null +++ b/sysroot/usr/src/mcpu/crt/crt1.S @@ -0,0 +1,58 @@ +; MCPU HLL MODEL adapter. + +.global __mcpu_crt_startup +.global init +.global life + +.init +__mcpu_crt_hll_model: +_start: + CALL __mcpu_crt_startup + + ; init(int64 argc, PTR64 argv) + LDSP r64@0x7fe00, 8 + LDSP r64@0x7fe08, 16 + ADJSP -16 + STSP 0, r64@0x7fe00 + STSP 8, r64@0x7fe08 + CALL init + ADJSP 16 + + CLR r32@0x7fe10 + ICMP r32@0x7fffc, r32@0x7fe10 + JZ .L_init_ok + EXIT r32@0x7fffc + +.L_init_ok: + END + +.text +_life: +.L_life_loop: + CALL life + + ; LIFE_CONTINUE == INT32_MIN == -2147483648. + LDI r32@0x7fe10, -2147483648 + ICMP r32@0x7fffc, r32@0x7fe10 + JZ .L_life_continue + + CLR r32@0x7fe10 + ICMP r32@0x7fffc, r32@0x7fe10 + JZ .L_life_end + JS .L_life_reserved + + ; Positive status is a MODEL error. + EXIT r32@0x7fffc + +.L_life_continue: + END + JMP .L_life_loop + +.L_life_end: + EXIT r32@0x7fffc + +.L_life_reserved: + ; Negative values other than INT32_MIN are reserved by ABI v1. + ; Current CRT maps programmer-contract violation to status 1. + LDI r32@0x7fe10, 1 + EXIT r32@0x7fe10 diff --git a/sysroot/usr/src/mcpu/crt/crti.S b/sysroot/usr/src/mcpu/crt/crti.S new file mode 100644 index 0000000..042a1b0 --- /dev/null +++ b/sysroot/usr/src/mcpu/crt/crti.S @@ -0,0 +1,67 @@ +; Common MCPU HLL startup support. +; +; This object is part of LibMCPU CRT. It validates McpuStartup, initializes +; the target runtime hooks, and records whether normal HLL lifetime has begun. + +.global __mcpu_libc_init +.global __mcpu_init_array +.global __mcpu_crt_startup +.global __mcpu_crt_state + +.data +.align 8 +__mcpu_crt_state: + .integer 64, 0 + +.text +__mcpu_crt_startup: + ; CALL pushed one 8-byte return PC. McpuStartup therefore begins at + ; SP+8 while this helper is active. + CLR r64@0x7ff00 + STORE __mcpu_crt_state, r64@0x7ff00 + + ; McpuStartup.version == 1. + LDSP r32@0x7ff08, 8 + LDI r32@0x7ff0c, 1 + ICMP r32@0x7ff08, r32@0x7ff0c + JNZ .L_startup_failure + + ; McpuStartup.size == 32. + LDSP r32@0x7ff08, 12 + LDI r32@0x7ff0c, 32 + ICMP r32@0x7ff08, r32@0x7ff0c + JNZ .L_startup_failure + + ; HLL argc is int64. Reject uint64 values with the sign bit set, + ; equivalently argc > 0x7fffffffffffffff. + LDSP r64@0x7ff10, 16 + CLR r64@0x7ff18 + ICMP r64@0x7ff10, r64@0x7ff18 + JS .L_startup_failure + + ; argv must be the canonical persistent array immediately following + ; the 32-byte McpuStartup structure. + LDSP r64@0x7ff20, 24 + LEASP r64@0x7ff28, 40 + ICMP r64@0x7ff20, r64@0x7ff28 + JNZ .L_startup_failure + + ; McpuStartup.reserved is zero in version 1. + LDSP r64@0x7ff30, 32 + ICMP r64@0x7ff30, r64@0x7ff18 + JNZ .L_startup_failure + + CALL __mcpu_libc_init + CALL __mcpu_init_array + + LDI r64@0x7ff00, 1 + STORE __mcpu_crt_state, r64@0x7ff00 + RET + +.L_startup_failure: + ; EXIT must happen with the top-level STACK state restored. Discard + ; this helper's return PC without RET, then terminate. _fini sees + ; PRE_RUNTIME state and therefore performs END only. + ADJSP 8 + LDI r32@0x7ff38, 1 + EXIT r32@0x7ff38 diff --git a/sysroot/usr/src/mcpu/crt/crtn.S b/sysroot/usr/src/mcpu/crt/crtn.S new file mode 100644 index 0000000..aeed44c --- /dev/null +++ b/sysroot/usr/src/mcpu/crt/crtn.S @@ -0,0 +1,20 @@ +; Common MCPU HLL finalization. + +.global __mcpu_crt_state +.global fini +.global __mcpu_fini_array +.global __mcpu_libc_fini + +.fini +_fini: + LOAD r64@0x7fd00, __mcpu_crt_state + LDI r64@0x7fd08, 1 + ICMP r64@0x7fd00, r64@0x7fd08 + JNZ .L_pre_runtime + + CALL fini + CALL __mcpu_fini_array + CALL __mcpu_libc_fini + +.L_pre_runtime: + END diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..ba58979 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,156 @@ + +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/utils $(LIBMPU_CPPFLAGS) $(LIBMPUIO_CPPFLAGS) +AM_CFLAGS = $(LIBMPU_CFLAGS) $(LIBMPUIO_CFLAGS) +AM_LDFLAGS = $(LIBMPU_LDFLAGS) $(LIBMPUIO_LDFLAGS) +test_programs = tst-errno-abi tst-errno-map tst-context tst-registers tst-overlap tst-instruction tst-bytecode tst-syscall tst-dup tst-assembler tst-control tst-stack tst-call tst-pointer tst-pointer-arithmetic tst-aggregate tst-frame tst-block-copy tst-memory tst-indirect-control tst-psw-abi tst-psw-write tst-byteorder tst-pointer-policy tst-bswap tst-mpu-coverage tst-immediate-exactness tst-conversion-flags tst-asm-directives tst-model-segments tst-lifecycle tst-relocatable tst-melf tst-dynamic +check_PROGRAMS = tst-tool-config tst-strip-fixture $(test_programs) +TESTS = $(test_programs) tst-runner.sh tst-stack-runner.sh tst-call-runner.sh tst-frame-runner.sh tst-block-copy-runner.sh tst-indirect-runner.sh tst-psw-abi-runner.sh tst-bswap-runner.sh tst-mpu-ops-runner.sh tst-immediate-exactness-runner.sh tst-lifecycle-runner.sh tst-relocatable-runner.sh tst-dynamic-runner.sh tst-line-runner.sh tst-run-memory-options.sh tst-stage-actions-runner.sh tst-readelf-runner.sh tst-tool-config-runner.sh tst-tool-inspection-runner.sh tst-library-search-runner.sh tst-link-needed-runner.sh tst-needed-runtime-runner.sh tst-symbol-scope-runner.sh tst-env-preload-runner.sh tst-abi3-final-runner.sh tst-ar-runner.sh tst-static-archive-link-runner.sh tst-partial-link-runner.sh tst-static-option-runner.sh tst-nm-runner.sh tst-objdump-runner.sh tst-strip-runner.sh tst-objcopy-runner.sh tst-hll-crt-runner.sh tst-verbose-runner.sh tst-doc-version-policy.sh +TESTS_ENVIRONMENT = PACKAGE_VERSION="$(PACKAGE_VERSION)" MCPU_LD_PRELOAD= CPU_AS="$(abs_top_builddir)/utils/mcpu-as" CPU_RUN="$(abs_top_builddir)/utils/mcpu-run" CPU_LD="$(abs_top_builddir)/utils/mcpu-ld" CPU_READELF="$(abs_top_builddir)/utils/mcpu-readelf" CPU_AR="$(abs_top_builddir)/utils/mcpu-ar" CPU_NM="$(abs_top_builddir)/utils/mcpu-nm" CPU_OBJDUMP="$(abs_top_builddir)/utils/mcpu-objdump" CPU_STRIP="$(abs_top_builddir)/utils/mcpu-strip" CPU_OBJCOPY="$(abs_top_builddir)/utils/mcpu-objcopy" STRIP_FIXTURE="$(abs_top_builddir)/tests/tst-strip-fixture" TOOL_CONFIG_TEST="$(abs_top_builddir)/tests/tst-tool-config" TEST_SRCDIR="$(abs_top_srcdir)/tests" MCPU_TEST_ROOT="$(abs_top_builddir)/sysroot" MCPU_HLL_FIXTURE_SRCDIR="$(abs_top_srcdir)/tests/fixtures/hll" +tst_errno_abi_SOURCES = tst-errno-abi.c +tst_errno_abi_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/sysroot/include +tst_errno_abi_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_errno_map_SOURCES = tst-errno-map.c +tst_errno_map_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cpu +tst_errno_map_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_context_SOURCES = tst-context.c +tst_context_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_registers_SOURCES = tst-registers.c +tst_registers_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_overlap_SOURCES = tst-overlap.c +tst_overlap_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_instruction_SOURCES = tst-instruction.c +tst_instruction_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_bytecode_SOURCES = tst-bytecode.c +tst_bytecode_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_syscall_SOURCES = tst-syscall.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_syscall_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_dup_SOURCES = tst-dup.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_dup_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/sysroot/include +tst_dup_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_assembler_SOURCES = tst-assembler.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_assembler_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_tool_config_SOURCES = tst-tool-config.c $(top_srcdir)/utils/mcpu-tool-options.c $(top_srcdir)/utils/mcpu-tool-options.h $(top_srcdir)/utils/mcpu-library-search.c $(top_srcdir)/utils/mcpu-library-search.h $(top_srcdir)/utils/mcpu-runtime.c $(top_srcdir)/utils/mcpu-runtime.h $(top_srcdir)/utils/mcpu-config.c $(top_srcdir)/utils/mcpu-config.h +tst_tool_config_LDADD = $(top_builddir)/utils/libmcpu-version.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_strip_fixture_SOURCES = tst-strip-fixture.c $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_strip_fixture_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +EXTRA_DIST = \ + fixtures/radd-pi.s \ + fixtures/stack.s \ + fixtures/call.s \ + fixtures/frame-pointer.s \ + fixtures/block-copy.s \ + fixtures/indirect-control.s \ + fixtures/psw-call-abi.s \ + fixtures/byte-swap.s \ + fixtures/mpu-ops.s \ + fixtures/model-lifecycle.s \ + fixtures/life-print.s \ + fixtures/argc-argv.s \ + fixtures/shared-libs/dynamic-main.s \ + fixtures/shared-libs/dynamic-liba.s \ + fixtures/shared-libs/dynamic-libb.s \ + fixtures/hll/program-main.S \ + fixtures/hll/model-pendulum-life.S \ + fixtures/hll/libc-stub.S \ + tst-runner.sh tst-stack-runner.sh tst-call-runner.sh tst-frame-runner.sh tst-block-copy-runner.sh tst-indirect-runner.sh tst-psw-abi-runner.sh tst-bswap-runner.sh tst-mpu-ops-runner.sh tst-immediate-exactness-runner.sh tst-lifecycle-runner.sh tst-relocatable-runner.sh tst-dynamic-runner.sh tst-line-runner.sh tst-run-memory-options.sh tst-stage-actions-runner.sh tst-readelf-runner.sh tst-tool-config-runner.sh tst-tool-inspection-runner.sh tst-library-search-runner.sh tst-link-needed-runner.sh tst-needed-runtime-runner.sh tst-symbol-scope-runner.sh tst-env-preload-runner.sh tst-abi3-final-runner.sh tst-ar-runner.sh tst-static-archive-link-runner.sh tst-partial-link-runner.sh tst-static-option-runner.sh tst-nm-runner.sh tst-objdump-runner.sh tst-strip-runner.sh tst-objcopy-runner.sh tst-hll-crt-runner.sh tst-verbose-runner.sh tst-doc-version-policy.sh + +tst_control_SOURCES = tst-control.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_control_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_stack_SOURCES = tst-stack.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_stack_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_call_SOURCES = tst-call.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_call_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_SOURCES = tst-pointer.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_arithmetic_SOURCES = tst-pointer-arithmetic.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_arithmetic_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_aggregate_SOURCES = tst-aggregate.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_aggregate_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_frame_SOURCES = tst-frame.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_frame_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_block_copy_SOURCES = tst-block-copy.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_block_copy_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_memory_SOURCES = tst-memory.c +tst_memory_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_indirect_control_SOURCES = tst-indirect-control.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_indirect_control_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_psw_abi_SOURCES = tst-psw-abi.c +tst_psw_abi_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_psw_write_SOURCES = tst-psw-write.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_psw_write_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_byteorder_SOURCES = tst-byteorder.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_byteorder_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_policy_SOURCES = tst-pointer-policy.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_policy_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_bswap_SOURCES = tst-bswap.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_bswap_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_mpu_coverage_SOURCES = tst-mpu-coverage.c +tst_mpu_coverage_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_immediate_exactness_SOURCES = tst-immediate-exactness.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_immediate_exactness_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_conversion_flags_SOURCES = tst-conversion-flags.c +tst_conversion_flags_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_asm_directives_SOURCES = tst-asm-directives.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_asm_directives_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_model_segments_SOURCES = tst-model-segments.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_model_segments_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_lifecycle_SOURCES = tst-lifecycle.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_lifecycle_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_relocatable_SOURCES = tst-relocatable.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_relocatable_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_melf_SOURCES = tst-melf.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-object.c $(top_srcdir)/utils/mcpu-object.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-exec.c $(top_srcdir)/utils/mcpu-exec.h $(top_srcdir)/utils/mcpu-dyn.c $(top_srcdir)/utils/mcpu-dyn.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_melf_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_dynamic_SOURCES = tst-dynamic.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-dyn.c $(top_srcdir)/utils/mcpu-dyn.h $(top_srcdir)/utils/mcpu-exec.c $(top_srcdir)/utils/mcpu-exec.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_dynamic_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) diff --git a/tests/fixtures/argc-argv.s b/tests/fixtures/argc-argv.s new file mode 100644 index 0000000..4c1d2ac --- /dev/null +++ b/tests/fixtures/argc-argv.s @@ -0,0 +1,44 @@ +; Minimal argc/argv and explicit "--" separator example. +; +; Assemble/link: +; ../utils/mcpu-as -o argc-argv.o argc-argv.s +; ../utils/mcpu-ld -o argc-argv --stack-size=4K argc-argv.o +; +; Run: +; ../utils/mcpu-run \ +; --on-step=print-int-regs:r64@0x00000,r8@0x00020,r8@0x00021 \ +; -- \ +; argc-argv --help -x +; +; Guest argv is: +; argv[0] = "argc-argv" +; argv[1] = "--help" +; argv[2] = "-x" +; +; The example prints argc, argv[1][2] ('h') and argv[2][1] ('x') as integers. +; Expected output: +; 3 104 120 + +.text +_start_life: +loop: + ; Persistent McpuStartup at SP: + ; SP+8 = argc + ; SP+16 = argv (PTR64 to argv[0]) + LDSP r64@0x00000, 8 + LDSP r64@0x00008, 16 + + ; argv[1] + PADDI r64@0x00010, r64@0x00008, 8 + LOADP r64@0x00018, r64@0x00010 + PADDI r64@0x00018, r64@0x00018, 2 + LOADP r8@0x00020, r64@0x00018 + + ; argv[2] + PADDI r64@0x00010, r64@0x00008, 16 + LOADP r64@0x00018, r64@0x00010 + PADDI r64@0x00018, r64@0x00018, 1 + LOADP r8@0x00021, r64@0x00018 + + END + JMP loop diff --git a/tests/fixtures/block-copy.s b/tests/fixtures/block-copy.s new file mode 100644 index 0000000..81422e2 --- /dev/null +++ b/tests/fixtures/block-copy.s @@ -0,0 +1,46 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o block-copy.o block-copy.s +; ../utils/mcpu-ld --stack-size=512 -o block-copy block-copy.o +; ../utils/mcpu-run --int-reg=r64@0x00028 block-copy +; +; Expected output: 22 +; +; LibCPU block-copy example. +; +; 1. Copy a 32-byte object from DATA to a local STACK object with BMOVI. +; 2. Use BMOV with a runtime byte count to perform an overlapping move +; inside that STACK object. BMOV has memmove semantics. +; 3. Read the moved value back through LOADP. Expected result: 22. + +.data +.align 8 +source_object: + .integer 64, 11 + .integer 64, 22 + .integer 64, 33 + .integer 64, 44 + +.text +_start_life: + ; Source PTR64 -> DATA object. + LEA r64@0x00000, source_object + + ; Reserve 32 bytes for a local object and obtain its STACK PTR64. + ADJSP -32 + LEASP r64@0x00008, 0 + + ; Fixed-size structure copy: DATA -> STACK. + BMOVI r64@0x00008, r64@0x00000, 32 + + ; Overlapping move inside the local object: + ; bytes [0..15] -> [8..23]. memmove semantics are required. + PADDI r64@0x00010, r64@0x00008, 8 + LDI r64@0x00018, 16 + BMOV r64@0x00010, r64@0x00008, r64@0x00018 + + ; The third 64-bit slot now contains the old second slot: 22. + PADDI r64@0x00020, r64@0x00008, 16 + LOADP r64@0x00028, r64@0x00020 + + ADJSP 32 + HALT diff --git a/tests/fixtures/byte-swap.s b/tests/fixtures/byte-swap.s new file mode 100644 index 0000000..7f666d0 --- /dev/null +++ b/tests/fixtures/byte-swap.s @@ -0,0 +1,15 @@ +; Byte-swap example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o byte-swap.o byte-swap.s +; ../utils/mcpu-ld --stack-size=512 -o byte-swap byte-swap.o +; ../utils/mcpu-run --int-reg=r64@0x00010 byte-swap +; +; Expected output: 42 +; +.text +_start_life: + LDI r64@0x00000, 42 + BSWAP r64@0x00008, r64@0x00000 + BSWAP r64@0x00010, r64@0x00008 + HALT diff --git a/tests/fixtures/call.s b/tests/fixtures/call.s new file mode 100644 index 0000000..bfc9965 --- /dev/null +++ b/tests/fixtures/call.s @@ -0,0 +1,39 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o call.o call.s +; ../utils/mcpu-ld --stack-size=128 -o call call.o +; ../utils/mcpu-run --int-reg=r64@0x00010 call +; +; Expected output: 33 +; +; LibCPU CALL ABI example. +; +; add2(a,b): this two-r64 argument block is exactly 16 bytes, so the +; traditional right-to-left PUSH sequence also satisfies the formal ABI. +; CALL then pushes +; the 64-bit return PC, therefore on callee entry: +; SP+0 = return address +; SP+8 = first argument a +; SP+16 = second argument b +; +; Before CALL, SP is 16-byte aligned. The r64 result is returned in the canonical ABI return view +; r64@0x7fff8. The caller removes its own arguments with ADJSP. + +.text +_start_life: + + LDI r64@0x00000, 11 + LDI r64@0x00008, 22 + + PUSH r64@0x00008 ; second/rightmost argument b + PUSH r64@0x00000 ; first/leftmost argument a + CALL add2 + ADJSP 16 ; caller cleanup + + MOV r64@0x00010, r64@0x7fff8 + HALT + +add2: + LDSP r64@0x00100, 8 ; a + LDSP r64@0x00108, 16 ; b + IADD r64@0x7fff8, r64@0x00100, r64@0x00108 + RET diff --git a/tests/fixtures/frame-pointer.s b/tests/fixtures/frame-pointer.s new file mode 100644 index 0000000..6c6934b --- /dev/null +++ b/tests/fixtures/frame-pointer.s @@ -0,0 +1,63 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o frame-pointer.o frame-pointer.s +; ../utils/mcpu-ld --stack-size=512 -o frame-pointer frame-pointer.o +; ../utils/mcpu-run --int-reg=r64@0x00008 frame-pointer +; +; Expected output: 42 +; +; LibCPU optional frame-pointer and dynamic STACK allocation example. +; +; demo(5) builds a fixed frame, creates a 24-byte dynamic object below it, +; accesses a fixed local through FP while SP is moving, and returns 42 in +; the canonical r64 return register. + +.text +_start_life: + LDI r64@0x00000, 5 + ADJSP -16 ; 16-byte ABI argument block + STSP 0, r64@0x00000 + CALL demo + ADJSP 16 + MOV r64@0x00008, r64@0x7fff8 + HALT + +demo: + PUSHFP + SETFP + ADJSP -16 + + ; First explicit argument. With FP in use the layout is: + ; FP+0 saved caller FP + ; FP+8 return PC + ; FP+16 first explicit argument + LDFP r64@0x00100, 16 + + ; Fixed local at FP-8. + STFP -8, r64@0x00100 + LEAFP r64@0x00108, -8 + + ; Save the current stack top as a dynamic-scope mark. + LEASP r64@0x00110, 0 + + ; Dynamically reserve 24 bytes: new_SP = old_SP - 24. + LDI r64@0x00118, 24 + PSUB r64@0x00120, r64@0x00110, r64@0x00118 + SETSP r64@0x00120 + + ; Put 37 in the dynamic object at current SP. + LDI r64@0x00128, 37 + LEASP r64@0x00130, 0 + STOREP r64@0x00130, r64@0x00128 + + ; FP-relative access is stable although SP moved dynamically. + LDFP r64@0x00138, -8 + LOADP r64@0x00140, r64@0x00130 + IADD r64@0x7fff8, r64@0x00138, r64@0x00140 + + ; Restore the nested dynamic scope explicitly. + SETSP r64@0x00110 + + ; Destroy the complete frame below FP, restore caller FP and return. + RESTSP + POPFP + RET diff --git a/tests/fixtures/hll/libc-stub.S b/tests/fixtures/hll/libc-stub.S new file mode 100644 index 0000000..d4fd878 --- /dev/null +++ b/tests/fixtures/hll/libc-stub.S @@ -0,0 +1,42 @@ +; Test/development MCPU Libc emulator. +; +; This source is NEVER installed. The register marker is intentionally a +; testing convention only; it is not part of the CRT <-> MCPU Libc ABI. + +.global __mcpu_libc_init +.global __mcpu_init_array +.global __mcpu_fini_array +.global __mcpu_libc_fini + +.text +__mcpu_libc_init: + CLR r64@0x70008 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 1 + RET + +__mcpu_init_array: + LDI r64@0x70008, 1 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 2 + RET + +__mcpu_fini_array: + LDI r64@0x70008, 4 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 5 + RET + +__mcpu_libc_fini: + LDI r64@0x70008, 5 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 6 + RET + +.L_stub_bad: + LDI r64@0x70000, 999 + RET diff --git a/tests/fixtures/hll/model-pendulum-life.S b/tests/fixtures/hll/model-pendulum-life.S new file mode 100644 index 0000000..5ad6ddb --- /dev/null +++ b/tests/fixtures/hll/model-pendulum-life.S @@ -0,0 +1,133 @@ +; Assembler emulator of future mcpu-cc MODEL output. +; +; The lifecycle shape is a compact pendulum-style MODEL regression fixture. +; INIT snapshots CONTROL +; into DATA, LIFE advances one model step and publishes one TRACE frame, and +; fini() marks graceful model finalization. +; +; This reduced fixture tests the HLL MODEL/CRT protocol, not RK4 numerics. + +.global init +.global life +.global fini + +.control +.align 8 +control_n: + .integer 64, 3 +control_error_step: + .integer 64, 0 + +.data +.align 8 +model_n: + .integer 64, 0 +model_error_step: + .integer 64, 0 +model_step: + .integer 64, 0 +model_fini_marker: + .integer 64, 0 + +.trace +.align 8 +trace_step: + .integer 64, 0 + +.text +; int32 init(int64 argc, PTR64 argv) +init: + LDI r64@0x70200, 2 + ICMP r64@0x70000, r64@0x70200 + JNZ .L_init_bad_order + + ; The test invokes the MODEL without extra arguments. + LDSP r64@0x70208, 8 + LDI r64@0x70210, 1 + ICMP r64@0x70208, r64@0x70210 + JNZ .L_init_bad_args + + ; Negative CONTROL.n deliberately requests an init failure. + LEA r64@0x70218, control_n + LOADP r64@0x70220, r64@0x70218 + CLR r64@0x70228 + ICMP r64@0x70220, r64@0x70228 + JS .L_init_fail + + STORE model_n, r64@0x70220 + LEA r64@0x70218, control_error_step + LOADP r64@0x70230, r64@0x70218 + STORE model_error_step, r64@0x70230 + + CLR r64@0x70238 + STORE model_step, r64@0x70238 + STORE model_fini_marker, r64@0x70238 + + LDI r64@0x70000, 3 + CLR r32@0x7fffc + RET + +.L_init_fail: + LDI r64@0x70000, 3 + LDI r32@0x7fffc, 17 + RET + +.L_init_bad_order: + LDI r64@0x70000, 910 + LDI r32@0x7fffc, 18 + RET + +.L_init_bad_args: + LDI r64@0x70000, 911 + LDI r32@0x7fffc, 19 + RET + +; int32 life(void) +life: + LOAD r64@0x70300, model_step + LDI r64@0x70308, 1 + IADD r64@0x70300, r64@0x70300, r64@0x70308 + STORE model_step, r64@0x70300 + + ; Optional positive LIFE error requested by CONTROL.error_step. + LOAD r64@0x70310, model_error_step + CLR r64@0x70318 + ICMP r64@0x70310, r64@0x70318 + JZ .L_no_life_error + ICMP r64@0x70300, r64@0x70310 + JNZ .L_no_life_error + LDI r32@0x7fffc, 9 + RET + +.L_no_life_error: + ; Pendulum-style sequential stop: step == n + 1 means natural end. + LOAD r64@0x70320, model_n + IADD r64@0x70328, r64@0x70320, r64@0x70308 + ICMP r64@0x70300, r64@0x70328 + JZ .L_life_end + + LEA r64@0x70330, trace_step + STOREP r64@0x70330, r64@0x70300 + + ; LIFE_CONTINUE == INT32_MIN. + LDI r32@0x7fffc, -2147483648 + RET + +.L_life_end: + CLR r32@0x7fffc + RET + +fini: + LDI r64@0x70200, 3 + ICMP r64@0x70000, r64@0x70200 + JNZ .L_model_fini_bad + + LDI r64@0x70000, 4 + LDI r64@0x70208, 1 + STORE model_fini_marker, r64@0x70208 + MOV r64@0x70010, r64@0x70208 + RET + +.L_model_fini_bad: + LDI r64@0x70000, 912 + RET diff --git a/tests/fixtures/hll/program-main.S b/tests/fixtures/hll/program-main.S new file mode 100644 index 0000000..3161343 --- /dev/null +++ b/tests/fixtures/hll/program-main.S @@ -0,0 +1,48 @@ +; Assembler emulator of future mcpu-cc PROGRAM output. +; +; It intentionally defines only main() and fini(); lifecycle symbols belong +; to CRT and must be inserted automatically by mcpu-ld. + +.global main +.global fini + +.text +main: + ; Startup hooks must already have completed. + LDI r64@0x70100, 2 + ICMP r64@0x70000, r64@0x70100 + JNZ .L_main_bad + + ; argc must include argv[0] plus the two test arguments. + LDSP r64@0x70108, 8 + LDI r64@0x70110, 3 + ICMP r64@0x70108, r64@0x70110 + JNZ .L_main_bad + + ; argv[argc] is the required NULL terminator. + LDSP r64@0x70118, 16 + PADDI r64@0x70120, r64@0x70118, 24 + LOADP r64@0x70128, r64@0x70120 + CLR r64@0x70130 + ICMP r64@0x70128, r64@0x70130 + JNZ .L_main_bad + + LDI r64@0x70000, 3 + LDI r32@0x7fffc, 42 + RET + +.L_main_bad: + LDI r64@0x70000, 900 + LDI r32@0x7fffc, 43 + RET + +fini: + LDI r64@0x70100, 3 + ICMP r64@0x70000, r64@0x70100 + JNZ .L_fini_bad + LDI r64@0x70000, 4 + RET + +.L_fini_bad: + LDI r64@0x70000, 901 + RET diff --git a/tests/fixtures/indirect-control.s b/tests/fixtures/indirect-control.s new file mode 100644 index 0000000..1e9d0e3 --- /dev/null +++ b/tests/fixtures/indirect-control.s @@ -0,0 +1,39 @@ +; Indirect CALLP/JMPP control-flow example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o indirect-control.o indirect-control.s +; ../utils/mcpu-ld --stack-size=512 -o indirect-control indirect-control.o +; ../utils/mcpu-run --int-reg=r64@0x00020 indirect-control +; +; Expected output: 42 +; +.data + +.align 8 +dispatch: + .cptr indirect_function + .cptr done + +.text +_start_life: + + ; Load a PROGRAM PTR64 from DATA and call through it. + LEA r64@0x00000, dispatch + LOADP r64@0x00008, r64@0x00000 + CALLP r64@0x00008 + + ; Select the second entry and jump through it. + PADDI r64@0x00000, r64@0x00000, 8 + LOADP r64@0x00010, r64@0x00000 + JMPP r64@0x00010 + + ; JMPP must skip this path. + LDI r64@0x00020, 99 + HALT + +indirect_function: + LDI r64@0x00020, 42 + RET + +done: + HALT diff --git a/tests/fixtures/life-print.s b/tests/fixtures/life-print.s new file mode 100644 index 0000000..a1de41f --- /dev/null +++ b/tests/fixtures/life-print.s @@ -0,0 +1,30 @@ +; Minimal LIFE-stage action example. +; +; Assemble/link: +; ../utils/mcpu-as -o life-print.o life-print.s +; ../utils/mcpu-ld -o life-print --stack-size=1K life-print.o +; +; Run three LIFE cycles and print two registers on one line after each END: +; ../utils/mcpu-run \ +; --steps=3 \ +; --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ +; -- \ +; life-print +; +; Expected output: +; 1 10 +; 2 20 +; 3 30 + +.text +_start_life: + LDI r64@0x00000, 1 + LDI r64@0x00008, 10 + LDI r64@0x00010, 1 + LDI r64@0x00018, 10 + +loop: + END + IADD r64@0x00000, r64@0x00000, r64@0x00010 + IADD r64@0x00008, r64@0x00008, r64@0x00018 + JMP loop diff --git a/tests/fixtures/model-lifecycle.s b/tests/fixtures/model-lifecycle.s new file mode 100644 index 0000000..4c190d5 --- /dev/null +++ b/tests/fixtures/model-lifecycle.s @@ -0,0 +1,53 @@ +; Build from examples/: +; ../utils/mcpu-as -o model-lifecycle.o model-lifecycle.s +; ../utils/mcpu-ld --stack-size=512 -o model-lifecycle model-lifecycle.o +; +; Run one INIT-only pass or one LIFE step: +; ../utils/mcpu-run --init-only --int-reg=r64@0x00000 model-lifecycle +; ../utils/mcpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle +; +; Expected outputs: 40 and 42 respectively. +; +; LibCPU lifecycle/model-segment example. +; cpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle -> 42 +; cpu-run --init-only --int-reg=r64@0x00000 model-lifecycle -> 40 + +.control +model_step: + .real 64, 1.0 + +.input +external_delta: + .integer 64, 2 + +.output +current_state: + .integer 64, 0 + +.trace +telemetry_state: + .integer 64, 0 + +.init +_start: + LDI r64@0x00000, 40 + END + +.text +_life: +loop: + LEA r64@0x00008, external_delta + LOADP r64@0x00010, r64@0x00008 + IADD r64@0x00000, r64@0x00000, r64@0x00010 + + LEA r64@0x00018, current_state + STOREP r64@0x00018, r64@0x00000 + LEA r64@0x00020, telemetry_state + STOREP r64@0x00020, r64@0x00000 + + END + JMP loop + +.fini +_fini: + END diff --git a/tests/fixtures/mpu-ops.s b/tests/fixtures/mpu-ops.s new file mode 100644 index 0000000..49d4f97 --- /dev/null +++ b/tests/fixtures/mpu-ops.s @@ -0,0 +1,25 @@ +; LibMPU-backed arithmetic/conversion operation example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o mpu-ops.o mpu-ops.s +; ../utils/mcpu-ld --stack-size=512 -o mpu-ops mpu-ops.o +; ../utils/mcpu-run --int-reg=r64@0x00020 mpu-ops +; +; Expected output: 42 +; +.text +_start_life: + + LDI r64@0x00000, 6 + LDI r64@0x00008, 7 + IMUL r128@0x00010, r64@0x00000, r64@0x00008 + ICPY r64@0x00020, r128@0x00010 + + RCONST r64@0x00028, ONE + CRTOC c128@0x00030, r64@0x00028 + CISEQ r64@0x00040, c128@0x00030, c128@0x00030 + IINC r64@0x00048, r64@0x00040 + IDEC r64@0x00050, r64@0x00048 + ISUB r64@0x00020, r64@0x00020, r64@0x00050 + IADD r64@0x00020, r64@0x00020, r64@0x00040 + HALT diff --git a/tests/fixtures/psw-call-abi.s b/tests/fixtures/psw-call-abi.s new file mode 100644 index 0000000..3dccab3 --- /dev/null +++ b/tests/fixtures/psw-call-abi.s @@ -0,0 +1,36 @@ +; PSW volatility across the CALL ABI example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o psw-call-abi.o psw-call-abi.s +; ../utils/mcpu-ld --stack-size=512 -o psw-call-abi psw-call-abi.o +; ../utils/mcpu-run --int-reg=r64@0x00010 psw-call-abi +; +; Expected output: 42 +; +.text +_start_life: + + ; Establish ZF=1 before the call: -1 + 1 -> 0 in signed r8. + LDI r8@0x00000, -1 + LDI r8@0x00001, 1 + IADD r8@0x00002, r8@0x00000, r8@0x00001 + + ; CALL itself preserves PSW, but the callee is free to clobber + ; PSW[15:0]. This function deliberately clears ZF with 1 + 1. + CALL clobber_status + + ; The old ZF is not part of the calling convention. Observe the + ; callee-produced ZF=0 and report 42. + JNZ status_is_volatile + LDI r64@0x00010, 99 + HALT + +clobber_status: + LDI r8@0x00003, 1 + LDI r8@0x00004, 1 + IADD r8@0x00005, r8@0x00003, r8@0x00004 + RET + +status_is_volatile: + LDI r64@0x00010, 42 + HALT diff --git a/tests/fixtures/radd-pi.s b/tests/fixtures/radd-pi.s new file mode 100644 index 0000000..2921d91 --- /dev/null +++ b/tests/fixtures/radd-pi.s @@ -0,0 +1,24 @@ +; High-precision real arithmetic example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o radd-pi.o radd-pi.s +; ../utils/mcpu-ld --stack-size=512 -o radd-pi radd-pi.o +; ../utils/mcpu-run --digits=60 --real-reg=r4096@0x00400 radd-pi +; +; Expected output starts with: +; 5.14159265358979323846264338327950288419716939937510582097 +; +.data + +.align 16 +result: + .real 4096, 0.0 + +.text +_start_life: + + LDR r4096@0x00000, 2.0 + RCONST r4096@0x00200, PI + RADD r4096@0x00400, r4096@0x00000, r4096@0x00200 + STORE result, r4096@0x00400 + HALT diff --git a/tests/fixtures/shared-libs/dynamic-liba.s b/tests/fixtures/shared-libs/dynamic-liba.s new file mode 100644 index 0000000..f35cfe2 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-liba.s @@ -0,0 +1,18 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; Shared-library implementation of foo: adds 2. +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/shared-libs/dynamic-libb.s b/tests/fixtures/shared-libs/dynamic-libb.s new file mode 100644 index 0000000..348b358 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-libb.s @@ -0,0 +1,18 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; Shared-library implementation of foo: adds 3. +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/shared-libs/dynamic-main.s b/tests/fixtures/shared-libs/dynamic-main.s new file mode 100644 index 0000000..f296429 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-main.s @@ -0,0 +1,35 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; ABI-v2 startup-dynamic executable example. +; main-program itself defines foo(), but cpu-ld --dynamic retains CALL foo as +; a preemptible runtime relocation. cpu-run --ldpreload can therefore replace +; it before _start executes. +; +; Without preload: foo adds 1 -> result 41. +; With dynamic-liba.so: foo adds 2 -> result 42. +; With dynamic-liba.so:dynamic-libb.so: rightmost strong GLOBAL wins, so +; dynamic-libb.so foo adds 3 -> result 43. +.global _life +.global foo + +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 + +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/stack.s b/tests/fixtures/stack.s new file mode 100644 index 0000000..9b45785 --- /dev/null +++ b/tests/fixtures/stack.s @@ -0,0 +1,32 @@ +; STACK instruction example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o stack.o stack.s +; ../utils/mcpu-ld --stack-size=128 -o stack stack.o +; ../utils/mcpu-run --int-reg=r64@0x00028 stack +; +; Expected output: 22 +; +.text +_start_life: + LDI r64@0x00000, 11 + LDI r64@0x00008, 22 + + ; STACK grows downward. cpu-run initializes SP to stack_size. + PUSH r64@0x00000 + PUSH r64@0x00008 + + ; Current stack top contains 22, SP+8 contains 11. + LDSP r64@0x00010, 8 + STSP 0, r64@0x00000 + + POP r64@0x00018 + POP r64@0x00020 + + ; Reserve a 16-byte local frame without automatic padding. + ADJSP -16 + STSP 0, r64@0x00008 + LDSP r64@0x00028, 0 + ADJSP 16 + + HALT diff --git a/tests/tst-abi3-final-runner.sh b/tests/tst-abi3-final-runner.sh new file mode 100755 index 0000000..7744775 --- /dev/null +++ b/tests/tst-abi3-final-runner.sh @@ -0,0 +1,78 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-abi3-final-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/override.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/override.o" "$TMP/override.s" + +# Conventional development link -> SONAME -> physical file layout. +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1.2.3" "$TMP/a.o" +ln -s liba.so.1.2.3 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" +"$CPU_LD" --shared --soname=liboverride.so.1 -o "$TMP/lib/liboverride.so.1" "$TMP/override.o" +ln -s liboverride.so.1 "$TMP/lib/liboverride.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -L"$TMP/lib" -la \ + -o "$TMP/main" "$TMP/main.o" + +"$CPU_READELF" -h -d "$TMP/lib/liba.so.1.2.3" > "$TMP/lib.txt" +grep -q 'ABI Version: 3' "$TMP/lib.txt" +grep -q 'DYN (Shared object file)' "$TMP/lib.txt" +grep -q 'SONAME' "$TMP/lib.txt" +grep -q 'Library soname: \[liba.so.1\]' "$TMP/lib.txt" + +"$CPU_READELF" -h -d "$TMP/main" > "$TMP/main.txt" +grep -q 'ABI Version: 3' "$TMP/main.txt" +grep -q 'EXEC (Executable file)' "$TMP/main.txt" +grep -q 'NEEDED' "$TMP/main.txt" +grep -q 'Shared library: \[liba.so.1\]' "$TMP/main.txt" +if grep -q 'liba.so.1.2.3' "$TMP/main.txt"; then + echo "physical shared-library filename leaked into DT_NEEDED" >&2 + exit 1 +fi + +# Frozen normal dependency path: automatic SONAME lookup and execution. +[ "`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Frozen explicit-preload interposition: explicit preload outranks normal graph. +[ "`MCPU_LD_PRELOAD=liboverride.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] + +exit 0 diff --git a/tests/tst-aggregate.c b/tests/tst-aggregate.c new file mode 100644 index 0000000..2311d0f --- /dev/null +++ b/tests/tst-aggregate.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" ADJSP -16\n" +" LEASP r64@0x00000, 0\n" +" LDI r64@0x00100, 11\n" +" LDI r64@0x00108, 22\n" +" ADJSP -32\n" +" STSP 0, r64@0x00000\n" +" STSP 8, r128@0x00100\n" +" CALL copy_pair\n" +" ADJSP 32\n" +" LDSP r128@0x00200, 0\n" +" IADD r64@0x00210, r64@0x00200, r64@0x00208\n" +" LEASP r64@0x00008, 0\n" +" ADJSP -16\n" +" STSP 0, r64@0x00008\n" +" CALL first_field\n" +" ADJSP 16\n" +" ADJSP 16\n" +" HALT\n" +"copy_pair:\n" +" LDSP r64@0x00300, 8\n" +" LDSP r128@0x00310, 16\n" +" STOREP r64@0x00300, r128@0x00310\n" +" RET\n" +"first_field:\n" +" LDSP r64@0x00400, 8\n" +" LOADP r64@0x7fff8, r64@0x00400\n" +" RET\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "stack alloc" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "aggregate ABI program run" ); + + CHECK( __cpu_sp == 512, "caller released local object and all arguments" ); + CHECK( eq64( 0x00200, "11" ), "aggregate return first field" ); + CHECK( eq64( 0x00208, "22" ), "aggregate return second field" ); + CHECK( eq64( 0x00210, "33" ), "aggregate return object preserved" ); + CHECK( eq64( 0x7fff8, "11" ), "pointer argument and scalar return" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu aggregate CALL ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-ar-runner.sh b/tests/tst-ar-runner.sh new file mode 100755 index 0000000..ffc939a --- /dev/null +++ b/tests/tst-ar-runner.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-ar-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + RET +EOS + +cat > "$TMP/very-long-member-name-for-mcpu.s" <<'EOS' +.global bar +.text +bar: + RET +EOS + +cat > "$TMP/c.s" <<'EOS' +.global baz +.text +baz: + RET +EOS + +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/very-long-member-name-for-mcpu.o" \ + "$TMP/very-long-member-name-for-mcpu.s" +"$CPU_AS" -o "$TMP/c.o" "$TMP/c.s" + +# rcs creates a deterministic UNIX archive, preserves command-line member +# order, supports a long member name, and writes a real ar symbol index. +"$CPU_AR" rcs "$TMP/libx.a" "$TMP/a.o" \ + "$TMP/very-long-member-name-for-mcpu.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +a.o +very-long-member-name-for-mcpu.o +EOS +cmp "$TMP/list.expected" "$TMP/list" + +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# p returns the exact member bytes. +"$CPU_AR" p "$TMP/libx.a" a.o > "$TMP/printed.o" +cmp "$TMP/a.o" "$TMP/printed.o" + +# x restores exact member bytes, including BSD extended-name members. +mkdir "$TMP/extract" +( + cd "$TMP/extract" + "$CPU_AR" x ../libx.a +) +cmp "$TMP/a.o" "$TMP/extract/a.o" +cmp "$TMP/very-long-member-name-for-mcpu.o" \ + "$TMP/extract/very-long-member-name-for-mcpu.o" + +# Archive output is deterministic across independent creations. +"$CPU_AR" rcs "$TMP/libx2.a" "$TMP/a.o" \ + "$TMP/very-long-member-name-for-mcpu.o" +cmp "$TMP/libx.a" "$TMP/libx2.a" + +# q always appends. Existing symbol-index state remains present. +"$CPU_AR" q "$TMP/libx.a" "$TMP/c.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +a.o +very-long-member-name-for-mcpu.o +c.o +EOS +cmp "$TMP/list.expected" "$TMP/list" +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# r replaces a member in place rather than moving it to the end. +cat > "$TMP/c.s" <<'EOS' +.global qux +.text +qux: + RET +EOS +"$CPU_AS" -o "$TMP/c.o" "$TMP/c.s" +"$CPU_AR" r "$TMP/libx.a" "$TMP/c.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cmp "$TMP/list.expected" "$TMP/list" +"$CPU_AR" p "$TMP/libx.a" c.o > "$TMP/printed-c.o" +cmp "$TMP/c.o" "$TMP/printed-c.o" + +# d removes named members and s rebuilds the symbol index explicitly. +"$CPU_AR" d "$TMP/libx.a" a.o +"$CPU_AR" s "$TMP/libx.a" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +very-long-member-name-for-mcpu.o +c.o +EOS +cmp "$TMP/list.expected" "$TMP/list" +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# A static MCPU archive may contain only valid current MELF ET_REL objects. +printf 'not MELF\n' > "$TMP/bad.o" +if "$CPU_AR" r "$TMP/bad.a" "$TMP/bad.o" >/dev/null 2>"$TMP/bad.err"; then + echo "mcpu-ar unexpectedly accepted a non-MELF member" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" +test ! -e "$TMP/bad.a" + +"$CPU_AR" --version | grep -Fq "$PACKAGE_VERSION" +"$CPU_AR" --help | grep -q 'rcs' +"$CPU_AR" --help | grep -q 'symbol index' + +exit 0 diff --git a/tests/tst-asm-directives.c b/tests/tst-asm-directives.c new file mode 100644 index 0000000..e86f46e --- /dev/null +++ b/tests/tst-asm-directives.c @@ -0,0 +1,84 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",m); return 1; } } while(0) + +static const char src[] = +".equ REG0, REGBASE+0\n" +".equ REGBASE, 0x03000\n" +".equ STEP, 4*512\n" +".data\n" +"ucs:\n" +" .ucs2 \"Андрей\"\n" +"utf:\n" +" .utf8 \"Андрей\"\n" +"raw:\n" +" .byte 1, 2+3, STEP/512, 0xff\n" +"ints:\n" +" .integer 16, 0x0410, 0x043d, 0\n" +"ints8:\n" +" .int 8, 65, 66, 0\n" +" .align 4*4\n" +"vals:\n" +" .integer 64, 11, 42\n" +".text\n" +"start:\n" +" LDI r64@REG0, 40+2\n" +" LOAD r64@(REG0+8), vals+8\n" +" HALT\n"; + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + int rc, i; + static const __mpu_uint16_t ucs_expect[] = + { + 0x0410, 0x043d, 0x0434, 0x0440, 0x0435, 0x0439, 0 + }; + static const unsigned char utf_expect[] = + { + 0xd0, 0x90, 0xd0, 0xbd, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb9, 0 + }; + static const unsigned char raw_expect[] = + { + 1, 5, 4, 0xff + }; + static const char bad_ucs[] = ".data\n.ucs2 \"\\U0001F600\"\n.text\nHALT\n"; + static const char bad_byte[] = ".data\n.byte 256\n.text\nHALT\n"; + static const char bad_cycle[] = ".equ A, B+1\n.equ B, A+1\n.text\nHALT\n"; + + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.data_size == 80, "DATA size including NULL guard, strings, raw bytes, integer lists and alignment" ); + for( i = 0; i < 7; i++ ) CHECK( __cpu_data_get_u16( im.data + 16 + i * 2 ) == ucs_expect[i], ".ucs2 code unit" ); + CHECK( !memcmp( im.data + 30, utf_expect, sizeof( utf_expect ) ), ".utf8 exact UTF-8 bytes plus NUL" ); + CHECK( !memcmp( im.data + 43, raw_expect, sizeof( raw_expect ) ), ".byte expressions" ); + CHECK( __cpu_data_get_u16( im.data + 47 ) == 0x0410, ".integer first list element" ); + CHECK( __cpu_data_get_u16( im.data + 49 ) == 0x043d, ".integer second list element" ); + CHECK( __cpu_data_get_u16( im.data + 51 ) == 0, ".integer terminator" ); + CHECK( im.data[53] == 65 && im.data[54] == 66 && im.data[55] == 0, ".int alias and list" ); + for( i = 56; i < 64; i++ ) CHECK( im.data[i] == 0, ".align expression padding" ); + CHECK( __cpu_data_get_u64( im.data + 64 ) == 11, "first 64-bit integer list element" ); + CHECK( __cpu_data_get_u64( im.data + 72 ) == 42, "second 64-bit integer list element" ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + rc = __cpu_run(); + CHECK( rc == 0, "assembled expression program run" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x03000 ) ) == 42, "symbolic register address via forward .equ" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x03008 ) ) == 42, "DATA label+offset expression" ); + cpu_asm_image_free( &im ); + + CHECK( cpu_assemble_text( bad_ucs, &im, err, sizeof( err ) ) != 0, "strict UCS-2 rejects non-BMP character" ); + CHECK( strstr( err, "strict UCS-2" ) != 0, "strict UCS-2 diagnostic" ); + CHECK( cpu_assemble_text( bad_byte, &im, err, sizeof( err ) ) != 0, ".byte rejects >8-bit value" ); + CHECK( cpu_assemble_text( bad_cycle, &im, err, sizeof( err ) ) != 0, "cyclic .equ rejected" ); + + __cpu_free_context(); + puts( "libcpu assembler directives/expressions regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-assembler.c b/tests/tst-assembler.c new file mode 100644 index 0000000..9b31edc --- /dev/null +++ b/tests/tst-assembler.c @@ -0,0 +1,60 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +static const char src[]= +".data\n" +"big:\n" +" .integer 128, 123456789012345678901234567890\n" +"rv:\n" +" .real 128, 1.25\n" +"cv:\n" +" .complex 128, 1.5, -2.5\n" +".text\n" +" LDI r128@0x00000, -17\n" +" LDR r128@0x00010, -17.0\n" +" LDC c128@0x00020, -17.0r0+32.0j0\n" +" RCONST r128@0x00030, PI\n" +" IADD r128@0x00040, r128@0x00000, r128@0x00000\n" +" RADD r128@0x00050, r128@0x00010, r128@0x00010\n" +" CADD c128@0x00060, c128@0x00020, c128@0x00020\n" +" LOAD r128@0x00070, big\n" +" HALT\n"; +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + mpu_int128_t ie; + mpu_real128_t re, rr; + mpu_wide_real_t cr, ci, er, ei; + int rc; + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "code produced" ); + CHECK( im.data_size == 64, "DATA NULL guard plus directives size" ); + CHECK( !memcmp( im.data, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", CPU_DATA_NULL_GUARD_SIZE ), "DATA NULL guard bytes" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + rc = __cpu_run(); + CHECK( rc == 0, "assembled program run" ); + iatoi( ( mpu_int * ) ie, "-17", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 0 ), ie, NB_I128 ), "LDI" ); + ascii_to_real( ( mpu_real * ) re, "-17.0", NB_R128 ); + CHECK( r_cmp( ( mpu_real * ) __cpu_reg_ptr( 128, 16 ), ( mpu_real * ) re, NB_R128 ) == 0, "LDR" ); + _m_PI( ( mpu_real * ) rr, NB_R128 ); + CHECK( r_cmp( ( mpu_real * ) __cpu_reg_ptr( 128, 48 ), ( mpu_real * ) rr, NB_R128 ) == 0, "RCONST PI" ); + c_real_part( ( mpu_real * ) cr, ( mpu_complex * ) __cpu_reg_ptr( 128, 32 ), NB_R64 ); + c_imaginary( ( mpu_real * ) ci, ( mpu_complex * ) __cpu_reg_ptr( 128, 32 ), NB_R64 ); + ascii_to_real( ( mpu_real * ) er, "-17.0", NB_R64 ); + ascii_to_real( ( mpu_real * ) ei, "32.0", NB_R64 ); + CHECK( r_cmp( ( mpu_real * ) cr, ( mpu_real * ) er, NB_R64 ) == 0, "LDC real component" ); + CHECK( r_cmp( ( mpu_real * ) ci, ( mpu_real * ) ei, NB_R64 ) == 0, "LDC imag component" ); + iatoi( ( mpu_int * ) ie, "123456789012345678901234567890", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 112 ), ie, NB_I128 ), "LOAD DATA integer" ); + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu test assembler regression: PASS" ); + return( 0 ); + } diff --git a/tests/tst-block-copy-runner.sh b/tests/tst-block-copy-runner.sh new file mode 100755 index 0000000..c8d02c9 --- /dev/null +++ b/tests/tst-block-copy-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-block-copy-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/block-copy.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00028 "$base.melf"` +test "x$out" = "x22" diff --git a/tests/tst-block-copy.c b/tests/tst-block-copy.c new file mode 100644 index 0000000..463633b --- /dev/null +++ b/tests/tst-block-copy.c @@ -0,0 +1,171 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static __mpu_uint64_t +get_reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p; + __mpu_uint32_t psw; + __mpu_UNIT_t before[32]; + unsigned k; + char err[256]; + static const char src[]= + ".data\n" + ".align 8\n" + "v:\n" + " .integer 64, 1\n" + " .integer 64, 2\n" + " .integer 64, 3\n" + " .integer 64, 4\n" + ".text\n" + " LEA r64@0x00000, v\n" + " ADJSP -32\n" + " LEASP r64@0x00008, 0\n" + " BMOVI r64@0x00008, r64@0x00000, 32\n" + " LDI r64@0x00010, 16\n" + " BMOV r64@0x00008, r64@0x00000, r64@0x00010\n" + " HALT\n"; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 128 ) == 0, "stack alloc" ); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + + /* Decoder knows both block-copy signatures. */ + __cpu_pc = 0; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode LEA" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode ADJSP" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode LEASP" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode BMOVI" ); + CHECK( i.opcode == CPU_OP_BMOVI, "BMOVI opcode" ); + CHECK( i.length == CPU_OPCODE_SIZE + 2 * CPU_REG24_SIZE + CPU_IMM64_SIZE, "BMOVI length" ); + CHECK( i.operand[2].type == CPU_OPERAND_IMM64 && i.operand[2].value.imm64 == 32, "BMOVI count" ); + + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "assembled block-copy program" ); + CHECK( memcmp( __cpu_stack_ptr() + 96, __cpu_data_ptr() + CPU_DATA_NULL_GUARD_SIZE, 16 ) == 0, "BMOV runtime count DATA->STACK" ); + + /* Direct executor: overlapping copy must have memmove semantics. */ + for( k = 0; k < 32; k++ ) __cpu_stack_ptr()[64 + k] = (__mpu_UNIT_t)k; + memcpy( before, __cpu_stack_ptr() + 64, sizeof( before ) ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 68 ) == 0, "make overlap destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 64 ) == 0, "make overlap source" ); + set_reg_u64( 0x00108, p ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOVI; + regop( &i.operand[0], 0x00100 ); + regop( &i.operand[1], 0x00108 ); + i.operand[2].type = CPU_OPERAND_IMM64; + i.operand[2].value.imm64 = 16; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOVI overlap" ); + CHECK( memcmp( __cpu_stack_ptr() + 68, before, 16 ) == 0, "BMOVI uses memmove semantics" ); + + /* Register-sized form uses an unsigned r64 byte count. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 24 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOV; + regop( &i.operand[0], 0x00100 ); + regop( &i.operand[1], 0x00108 ); + regop( &i.operand[2], 0x00110 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV register count" ); + CHECK( memcmp( __cpu_stack_ptr() + 32, __cpu_data_ptr() + CPU_DATA_NULL_GUARD_SIZE, 24 ) == 0, "BMOV copied runtime byte count" ); + + /* Copying from PROGRAM is permitted. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 2 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV PROGRAM->DATA" ); + CHECK( __cpu_data_ptr()[CPU_DATA_NULL_GUARD_SIZE] == __cpu_program_ptr()[0] && __cpu_data_ptr()[CPU_DATA_NULL_GUARD_SIZE + 1] == __cpu_program_ptr()[1], "PROGRAM source bytes copied" ); + + /* PROGRAM is read-only as a destination; destination memory is unchanged. */ + memcpy( before, __cpu_program_ptr(), 16 ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK source" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 8 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "BMOV rejects PROGRAM destination" ); + CHECK( memcmp( before, __cpu_program_ptr(), 16 ) == 0, "PROGRAM unchanged on destination fault" ); + + /* Out-of-range source is detected before any destination bytes change. */ + memset( __cpu_stack_ptr() + 16, 0xa5, 16 ); + memcpy( before, __cpu_stack_ptr() + 16, 16 ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 16 ) == 0, "make atomic destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, __cpu_data_size() - 4 ) == 0, "make short source range" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 8 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "BMOV source range fault" ); + CHECK( memcmp( before, __cpu_stack_ptr() + 16, 16 ) == 0, "destination unchanged on source fault" ); + + /* Reserved segment fails as a pointer fault. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 0 ) == 0, "make reserved pointer" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 1 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "BMOV reserved source segment" ); + + /* PSW is not modified. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make PSW source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 0 ) == 0, "make PSW destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 1 ); + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV PSW test" ); + CHECK( __cpu_get_psw() == psw, "BMOV preserves PSW" ); + + /* Zero length is a valid no-op at one-past offsets of allocated segments. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, __cpu_data_size() ) == 0, "make DATA one-past" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, __cpu_stack_size() ) == 0, "make STACK one-past" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 0 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV zero byte no-op" ); + CHECK( get_reg_u64( 0x00110 ) == 0, "zero count register retained" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu block-copy regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-bswap-runner.sh b/tests/tst-bswap-runner.sh new file mode 100755 index 0000000..aec1668 --- /dev/null +++ b/tests/tst-bswap-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-bswap-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/byte-swap.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-bswap.c b/tests/tst-bswap.c new file mode 100644 index 0000000..aff9cbd --- /dev/null +++ b/tests/tst-bswap.c @@ -0,0 +1,105 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned bits, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = address; +} + +static int +do_bswap( unsigned bits, unsigned d, unsigned s ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BSWAP; + regop( &i.operand[0], bits, d ); + regop( &i.operand[1], bits, s ); + return( __cpu_exec( &i ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + __mpu_UNIT_t *src, *dst; + unsigned i; + __mpu_uint32_t psw; + const char *asrc= + ".text\n" + "LDI r64@0x00000, 42\n" + "BSWAP r64@0x00008, r64@0x00000\n" + "BSWAP r64@0x00010, r64@0x00008\n" + "HALT\n"; + + __cpu_init(); + CHECK( cpu_assemble_text( asrc, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size == 31, "BSWAP machine size is OPCODE16 + 2*REG24" ); + CHECK( __cpu_code_get_u16( im.code + 13 ) == CPU_OP_BSWAP, "first BSWAP opcode encoded" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "set BSWAP program" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "run BSWAP program" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x10 ) ) == 42, "double BSWAP round trip" ); + cpu_asm_image_free( &im ); + + /* r8 is a defined identity operation. */ + src = __cpu_reg_ptr( 8, 0x100 ); + dst = __cpu_reg_ptr( 8, 0x101 ); + *src = 0xa5; + *dst = 0; + CHECK( do_bswap( 8, 0x101, 0x100 ) == CPU_EXEC_SUCCESS, "BSWAP r8" ); + CHECK( *dst == 0xa5, "BSWAP r8 identity" ); + + /* Arbitrary wide physical registers are reversed byte for byte. */ + src = __cpu_reg_ptr( 256, 0x200 ); + dst = __cpu_reg_ptr( 256, 0x220 ); + for( i = 0; i < 32; i++ ) src[i] = (__mpu_UNIT_t)i; + memset( dst, 0, 32 ); + psw = PSW_CF_MASK | PSW_IF_MASK | PSW_TF_MASK; + __cpu_set_psw( psw ); + CHECK( do_bswap( 256, 0x220, 0x200 ) == CPU_EXEC_SUCCESS, "BSWAP r256" ); + for( i = 0; i < 32; i++ ) CHECK( dst[i] == (__mpu_UNIT_t)( 31 - i ), "BSWAP r256 byte reversal" ); + CHECK( __cpu_get_psw() == psw, "BSWAP preserves PSW" ); + + /* In-place BSWAP uses source-snapshot semantics. */ + src = __cpu_reg_ptr( 64, 0x300 ); + for( i = 0; i < 8; i++ ) src[i] = (__mpu_UNIT_t)( 0x10 + i ); + CHECK( do_bswap( 64, 0x300, 0x300 ) == CPU_EXEC_SUCCESS, "in-place BSWAP" ); + for( i = 0; i < 8; i++ ) CHECK( src[i] == (__mpu_UNIT_t)( 0x17 - i ), "in-place BSWAP snapshot result" ); + + /* Maximum physical width is supported. */ + src = __cpu_reg_ptr( 65536, 0x0000 ); + dst = __cpu_reg_ptr( 65536, 0x2000 ); + for( i = 0; i < 8192; i++ ) src[i] = (__mpu_UNIT_t)( i & 0xffU ); + CHECK( do_bswap( 65536, 0x2000, 0x0000 ) == CPU_EXEC_SUCCESS, "BSWAP r65536" ); + CHECK( dst[0] == src[8191] && dst[8191] == src[0], "BSWAP r65536 endpoints" ); + + /* Width mismatch is rejected before modifying destination. */ + { + cpu_instruction x; + __mpu_UNIT_t before[8]; + memset( x.operand, 0, sizeof( x.operand ) ); + x.opcode = CPU_OP_BSWAP; + regop( &x.operand[0], 64, 0x400 ); + regop( &x.operand[1], 32, 0x408 ); + memset( __cpu_reg_ptr( 64, 0x400 ), 0x5a, 8 ); + memcpy( before, __cpu_reg_ptr( 64, 0x400 ), 8 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_WIDTH_FAULT, "BSWAP width mismatch" ); + CHECK( memcmp( before, __cpu_reg_ptr( 64, 0x400 ), 8 ) == 0, "BSWAP width fault atomic" ); + } + + /* Assembler syntax intentionally uses raw r<bits> views, not c<bits>. */ + CHECK( cpu_assemble_text( ".text\nBSWAP c64@0x0, c64@0x8\nHALT\n", &im, err, sizeof( err ) ) != 0, + "assembler rejects complex spelling for raw BSWAP"); + + __cpu_free_context(); + puts( "libcpu BSWAP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-bytecode.c b/tests/tst-bytecode.c new file mode 100644 index 0000000..c4f885e --- /dev/null +++ b/tests/tst-bytecode.c @@ -0,0 +1,56 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +int +main( void ) +{ + cpu_register r = + { + 128, 0 + }; + __mpu_UNIT_t reg[3], code[18]; + mpu_int128_t exp; + cpu_instruction insn; + int rc; + CHECK( __cpu_encode_reg24( reg, &r ) == 0, "encode REG24" ); + code[0] = 0x01; + code[1] = 0x10; + memcpy( code + 2, reg, 3 ); + /* LDI */ + code[5] = 0xff; + code[6] = 0xff; + code[7] = 0xff; + code[8] = 0xff; + code[9] = 0xff; + code[10] = 0xff; + code[11] = 0xff; + code[12] = 0xef; + /* -17 */ + code[13] = 0x00; + code[14] = 0x01; + /* HALT */ + CHECK( __cpu_decode_at( code, 15, 0, &insn ) == CPU_EXEC_SUCCESS, + "decode-at LDI" ); + CHECK( insn.opcode == CPU_OP_LDI && insn.length == 13, + "decode-at LDI fields" ); + CHECK( __cpu_decode_at( code, 15, 13, &insn ) == CPU_EXEC_SUCCESS, + "decode-at HALT" ); + CHECK( insn.opcode == CPU_OP_HALT && insn.length == 2, + "decode-at HALT fields" ); + CHECK( __cpu_decode_at( code, 14, 13, &insn ) == CPU_EXEC_DECODE_FAULT, + "decode-at bounds" ); + __cpu_init(); + CHECK( __cpu_program_set( code, 15 ) == 0, "program mmap/load" ); + CHECK( __cpu_program_ptr() != 0, "program pointer" ); + CHECK( __cpu_program_size() == 15, "program size" ); + rc = __cpu_run(); + CHECK( rc == 0, "run" ); + CHECK( __cpu_pc == 15, "PC advanced" ); + CHECK( __cpu_gth() != 0, "HF set" ); + iatoi( ( mpu_int * ) exp, "-17", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 0 ), exp, NB_I128 ), "LDI int64 -> int128" ); + __cpu_free_context(); + puts( "libcpu bytecode/program-memory regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-byteorder.c b/tests/tst-byteorder.c new file mode 100644 index 0000000..6346c9e --- /dev/null +++ b/tests/tst-byteorder.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static int +raw_u64_matches_target( const __mpu_UNIT_t *p, __mpu_uint64_t v ) +{ + __mpu_UNIT_t x[8]; + __cpu_data_put_u64( x, v ); + return( memcmp( p, x, 8 ) == 0 ); +} + +int +main( void ) +{ + static const __mpu_UNIT_t canonical64[8] = + { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 + }; + static const char src[]= + ".data\n" + ".align 8\n" + "ival:\n" + " .integer 64, 1234605616436508552\n" + ".align 8\n" + "rval:\n" + " .real 64, 1.5\n" + ".align 16\n" + "zval:\n" + " .complex 128, 1.5, -2.0\n" + ".align 8\n" + "target_slot:\n" + " .cptr done\n" + ".align 8\n" + "copy:\n" + " .integer 64, 0\n" + ".text\n" + " LDI r64@0x00000, 1234605616436508552\n" + " LOAD r64@0x00008, ival\n" + " LEA r64@0x00010, ival\n" + " LEA r64@0x00018, copy\n" + " BMOVI r64@0x00018, r64@0x00010, 8\n" + " LOAD r64@0x00020, copy\n" + " CALL leaf\n" + " JMPP r64@0x00028\n" + "leaf:\n" + " RET\n" + "done:\n" + " HALT\n"; + static const char call_src[]= + ".text\n" + " CALL leaf\n" + " HALT\n" + "leaf:\n" + " RET\n"; + cpu_asm_image im, callim, alias; + __mpu_UNIT_t buf[8], real_expected[8], complex_expected[16]; + mpu_wide_real_t rr, ri; + mpu_wide_complex_t zz; + __mpu_uint64_t v = __mpu_UINT64_C( 0x1122334455667788 ), ptr, ret; + char err[256]; + int rc; + + __cpu_init(); + + /* The two byte-order domains are deliberately different concepts. */ + memset( buf, 0, sizeof( buf ) ); + __cpu_code_put_u64( buf, v ); + CHECK( !memcmp( buf, canonical64, 8 ), "CODE u64 is canonical MSB-first" ); + CHECK( __cpu_code_get_u64( buf ) == v, "CODE u64 round trip" ); + CHECK( CPU_CODE_BYTE_ORDER_MSB_FIRST == 1, "CODE byte-order ABI marker" ); + + memset( buf, 0, sizeof( buf ) ); + __cpu_data_put_u64( buf, v ); + CHECK( __cpu_data_get_u64( buf ) == v, "DATA/object u64 round trip" ); +#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1 + { + static const __mpu_UNIT_t le[8] = + { + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 + }; + CHECK( !memcmp( buf, le, 8 ), "target data u64 little-endian bytes" ); + } +#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1 + CHECK( !memcmp( buf, canonical64, 8 ), "target data u64 big-endian bytes" ); +#else +# error Unsupported LibMPU byte order for LibCPU byte-order regression +#endif + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + + /* First instruction is LDI: OPCODE16 + REG24 + canonical IMM64. */ + CHECK( im.code_size > 13, "assembled bytecode exists" ); + CHECK( im.code[0] == 0x01 && im.code[1] == 0x10, "LDI opcode canonical bytes" ); + CHECK( !memcmp( im.code + 5, canonical64, 8 ), "LDI IMM64 is canonical MSB-first" ); + + /* DATA directives create target object representation, not bytecode order. */ + CHECK( im.data_size >= 64, "assembled DATA image size" ); + CHECK( __cpu_data_get_u64( im.data + 16 ) == v, ".integer64 target data object" ); + CHECK( raw_u64_matches_target( im.data + 16, v ), ".integer64 raw target bytes" ); + + memset( rr, 0, sizeof( rr ) ); + CHECK( ascii_to_real( ( mpu_real * ) rr, "1.5", NB_R64 ) >= 0, "build expected real64" ); + memcpy( real_expected, rr, 8 ); + CHECK( !memcmp( im.data + 24, real_expected, 8 ), ".real64 follows LibMPU object representation" ); + + memset( rr, 0, sizeof( rr ) ); + memset( ri, 0, sizeof( ri ) ); + memset( zz, 0, sizeof( zz ) ); + CHECK( ascii_to_real( ( mpu_real * ) rr, "1.5", NB_R64 ) >= 0, "build complex real component" ); + CHECK( ascii_to_real( ( mpu_real * ) ri, "-2.0", NB_R64 ) >= 0, "build complex imaginary component" ); + c_gen_complex( ( mpu_complex * ) zz, ( mpu_real * ) rr, ( mpu_real * ) ri, NB_R64 ); + memcpy( complex_expected, zz, 16 ); + CHECK( !memcmp( im.data + 32, complex_expected, 16 ), ".complex128 follows LibMPU object representation" ); + + ptr = __cpu_data_get_u64( im.data + 48 ); + CHECK( __cpu_ptr_segment( ptr ) == CPU_PTR_SEG_PROGRAM, ".cptr stores PROGRAM PTR64 data object" ); + CHECK( __cpu_ptr_offset( ptr ) < im.code_size, ".cptr PROGRAM offset in range" ); + + /* Compatibility spelling remains accepted, but .cptr is canonical. */ + CHECK( cpu_assemble_text( ".data\n .codeptr x\n.text\nx:\n HALT\n", &alias, err, sizeof( err ) ) == 0, "legacy .codeptr alias accepted" ); + cpu_asm_image_free( &alias ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "stack alloc" ); + + /* Load source and BMOV destination preserve the complete object bytes. */ + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LDI" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x00000 ) ) == v, "LDI decoded canonical bits into target register object" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LOAD source" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00008 ), im.data + 16, 8 ), "LOAD preserves data object bytes" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LEA source" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LEA destination" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step BMOVI" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LOAD copied object" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00008 ), __cpu_reg_ptr( 64, 0x00020 ), 8 ), "LOAD-BMOV-LOAD is byte preserving" ); + CHECK( !memcmp( __cpu_data_ptr() + 56, im.data + 16, 8 ), "BMOV copied exact target object bytes" ); + + cpu_asm_image_free( &im ); + __cpu_program_free(); + __cpu_data_free(); + __cpu_stack_free(); + + /* CALL return PC is a STACK data object, not a canonical CODE64 field. */ + CHECK( cpu_assemble_text( call_src, &callim, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( callim.code, callim.code_size ) == 0, "call program set" ); + CHECK( __cpu_stack_alloc( 64 ) == 0, "call stack alloc" ); + __cpu_pc = 0; + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "CALL step" ); + CHECK( __cpu_sp == 56, "CALL pushed 8-byte return object" ); + ret = __cpu_data_get_u64( __cpu_stack_ptr() + 56 ); + CHECK( ret == 10, "CALL return PC target data representation decodes to next PC" ); + CHECK( raw_u64_matches_target( __cpu_stack_ptr() + 56, 10 ), "CALL return PC raw stack bytes use target data order" ); + cpu_asm_image_free( &callim ); + + __cpu_free_context(); + puts( "libcpu byte-order/object-representation regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-call-runner.sh b/tests/tst-call-runner.sh new file mode 100755 index 0000000..e0e6994 --- /dev/null +++ b/tests/tst-call-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-call-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/call.s" >/dev/null +"$CPU_LD" --stack-size=128 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x33" diff --git a/tests/tst-call.c b/tests/tst-call.c new file mode 100644 index 0000000..7363c83 --- /dev/null +++ b/tests/tst-call.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char leaf_src[]= +".text\n" +" CALL leaf\n" +" HALT\n" +"leaf:\n" +" RET\n"; + +static const char add_src[]= +".text\n" +" LDI r64@0x00000, 11\n" +" LDI r64@0x00008, 22\n" +" PUSH r64@0x00008\n" +" PUSH r64@0x00000\n" +" CALL add2\n" +" ADJSP 16\n" +" MOV r64@0x00010, r64@0x7fff8\n" +" HALT\n" +"add2:\n" +" LDSP r64@0x00100, 8\n" +" LDSP r64@0x00108, 16\n" +" IADD r64@0x7fff8, r64@0x00100, r64@0x00108\n" +" RET\n"; + +int +main( void ) +{ + cpu_asm_image im; + cpu_register rr; + mpu_int64_t thirty_three; + __mpu_uint64_t saved_pc; + __mpu_uint32_t psw; + char err[256]; + unsigned int widths[] = + { + 8, 16, 32, 64, 4096, 65536 + }; + size_t k; + + __cpu_init(); + + CHECK( CPU_RETURN_AREA_BEGIN == 0x0007e000U, "return area begin" ); + CHECK( CPU_RETURN_AREA_END == 0x00080000U, "return area end" ); + CHECK( CPU_RETURN_AREA_SIZE == 0x00002000U, "return area size" ); + for( k = 0; k < sizeof( widths ) / sizeof( widths[0] ); k++ ) + { + __mpu_size_t n = __cpu_reg_size( widths[k] ); + CHECK( __cpu_return_reg( &rr, widths[k] ) == 0, "canonical return register" ); + CHECK( rr.bits == widths[k], "return register width" ); + CHECK( rr.address == CPU_RETURN_AREA_END - (unsigned)n, "return register address formula" ); + } + CHECK( __cpu_return_reg( &rr, 7 ) != 0, "reject invalid return width" ); + + CHECK( cpu_assemble_text( leaf_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "leaf program set" ); + CHECK( __cpu_stack_alloc( 64 ) == 0, "leaf stack alloc" ); + __cpu_pc = 0; + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + + CHECK( __cpu_step() == CPU_EXEC_SUCCESS, "CALL step" ); + CHECK( __cpu_pc == 12, "CALL target" ); + CHECK( __cpu_sp == 56, "CALL pushed 8-byte return address" ); + memcpy( &saved_pc, __cpu_stack_ptr() + __cpu_sp, 8 ); + CHECK( saved_pc == 10, "CALL stored next PC" ); + CHECK( __cpu_get_psw() == psw, "CALL preserves PSW" ); + + CHECK( __cpu_step() == CPU_EXEC_SUCCESS, "RET step" ); + CHECK( __cpu_pc == 10, "RET restored PC" ); + CHECK( __cpu_sp == 64, "RET popped return address" ); + CHECK( __cpu_get_psw() == psw, "RET preserves PSW" ); + cpu_asm_image_free( &im ); + + __cpu_program_free(); + __cpu_stack_free(); + memset( __cpu_registers, 0, CPU_REGISTERS_MEM_SIZE_IN_UNITS ); + __cpu_clear_psw(); + + CHECK( cpu_assemble_text( add_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "add program set" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "add stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "CALL ABI run" ); + CHECK( __cpu_sp == 128, "caller cleanup restored empty SP" ); + + iatoi( ( mpu_int * ) thirty_three, "33", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x7fff8 ), thirty_three, NB_I64 ), "return value area result" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00010 ), thirty_three, NB_I64 ), "caller copied return value" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu CALL ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-context.c b/tests/tst-context.c new file mode 100644 index 0000000..6384d75 --- /dev/null +++ b/tests/tst-context.c @@ -0,0 +1,121 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +int +main( void ) +{ + __mpu_uint32_t psw; + + __cpu_init(); + + CHECK( __cpu_pc == 0, "PC after __cpu_init()" ); + CHECK( __cpu_sp == 0, "SP after __cpu_init()" ); + CHECK( __cpu_fp == 0, "FP after __cpu_init()" ); + CHECK( __cpu_get_psw() == 0, "PSW after __cpu_init()" ); + + /* Verify access to the complete 512 KiB register file. */ + __cpu_registers[0] = 0x5a; + __cpu_registers[CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1] = 0xa5; + CHECK( __cpu_registers[0] == 0x5a, "register file first byte" ); + CHECK( __cpu_registers[CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1] == 0xa5, + "register file last byte" ); + + /* Low PSW bits are maintained by LibMPU. */ + __mpu_stc(); + __mpu_stz(); + __mpu_stinx(); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_CF_MASK) != 0, "PSW reads LibMPU CF" ); + CHECK( (psw & PSW_ZF_MASK) != 0, "PSW reads LibMPU ZF" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "PSW reads LibMPU INXF" ); + + /* High PSW bits belong to LibCPU. */ + __cpu_sth(); + __cpu_stt(); + __cpu_sti(); + CHECK( __cpu_stmode( 2 ) == 0, "set CPU MODE" ); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) != 0, "PSW HF" ); + CHECK( (psw & PSW_TF_MASK) != 0, "PSW TF" ); + CHECK( (psw & PSW_IF_MASK) != 0, "PSW IF" ); + CHECK( __cpu_gtmode() == 2, "PSW MODE" ); + + /* CPU flag operations do not alter the LibMPU half of PSW. */ + __cpu_clh(); + __cpu_cmt(); + __cpu_cmi(); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) == 0, "clear HF" ); + CHECK( (psw & PSW_TF_MASK) == 0, "complement TF" ); + CHECK( (psw & PSW_IF_MASK) == 0, "complement IF" ); + CHECK( (psw & PSW_CF_MASK) != 0, "CF preserved by CPU flags" ); + CHECK( (psw & PSW_ZF_MASK) != 0, "ZF preserved by CPU flags" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "INXF preserved by CPU flags" ); + + /* Store/restore the complete logical PSW. */ + __cpu_set_psw( PSW_AF_MASK | PSW_OF_MASK | PSW_DOMF_MASK | + PSW_HF_MASK | PSW_IF_MASK | + ((__mpu_uint32_t)3 << PSW_MODE_SHIFT) ); + + psw = __cpu_get_psw(); + CHECK( psw == (PSW_AF_MASK | PSW_OF_MASK | PSW_DOMF_MASK | + PSW_HF_MASK | PSW_IF_MASK | + ((__mpu_uint32_t)3 << PSW_MODE_SHIFT)), + "complete PSW store/restore" ); + CHECK( __mpu_gta() == 1, "LibMPU AF after __cpu_set_psw()" ); + CHECK( __mpu_gto() == 1, "LibMPU OF after __cpu_set_psw()" ); + CHECK( __mpu_gtdom() == 1, "LibMPU DOMF after __cpu_set_psw()" ); + CHECK( __mpu_gtc() == 0, "LibMPU CF cleared by __cpu_set_psw()" ); + CHECK( __cpu_gth() == 1, "LibCPU HF after __cpu_set_psw()" ); + CHECK( __cpu_gti() == 1, "LibCPU IF after __cpu_set_psw()" ); + CHECK( __cpu_gtmode() == 3, "LibCPU MODE after __cpu_set_psw()" ); + + __cpu_clear_psw(); + CHECK( __cpu_get_psw() == 0, "clear complete PSW" ); + + __cpu_pc = 0x123456789abcdef0ULL; + __cpu_sp = 0x0fedcba987654321ULL; + __cpu_fp = 0x0123456789abcdefULL; + CHECK( __cpu_pc == 0x123456789abcdef0ULL, "PC access" ); + CHECK( __cpu_sp == 0x0fedcba987654321ULL, "SP access" ); + CHECK( __cpu_fp == 0x0123456789abcdefULL, "FP access" ); + + __cpu_free_context(); + CHECK( __cpu_pc == 0, "PC after __cpu_free_context()" ); + CHECK( __cpu_sp == 0, "SP after __cpu_free_context()" ); + CHECK( __cpu_fp == 0, "FP after __cpu_free_context()" ); + CHECK( __cpu_get_psw() == 0, "PSW after __cpu_free_context()" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu context regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu context/PSW regression: PASS\n" ); + printf( "register file: %u bytes\n", CPU_REGISTERS_MEM_SIZE_IN_UNITS ); + printf( "PSW: LibMPU bits 0..15, LibCPU bits 16..20\n" ); + + return( 0 ); +} diff --git a/tests/tst-control.c b/tests/tst-control.c new file mode 100644 index 0000000..7a3300f --- /dev/null +++ b/tests/tst-control.c @@ -0,0 +1,80 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + + +static const char alias_src[]= +".text\n" +" JZ end\n" +" JNZ end\n" +" JC end\n" +" JNC end\n" +" JO end\n" +" JNO end\n" +" JS end\n" +" JNS end\n" +"end:\n" +" HALT\n"; + +static const char src[]= +".text\n" +" JMP start\n" +"dead:\n" +" HALT\n" +"start:\n" +" LDI r64@0x00000, 3\n" +" LDI r64@0x00008, 1\n" +" CLR r64@0x00010\n" +"loop:\n" +" ISUB r64@0x00000, r64@0x00000, r64@0x00008\n" +" ICMP r64@0x00000, r64@0x00010\n" +" JNZ loop\n" +" JSET ZF, done\n" +" LDI r64@0x00018, 77\n" +"done:\n" +" LDI r64@0x00018, 42\n" +" HALT\n"; + +int +main( void ) +{ + cpu_asm_image im, aliases; + cpu_instruction insn; + mpu_int64_t zero, forty_two; + char err[256]; + int rc; + + __cpu_init(); + CHECK( cpu_assemble_text( alias_src, &aliases, err, sizeof( err ) ) == 0, err ); + CHECK( aliases.code_size == 90, "all eight jump aliases assembled" ); + cpu_asm_image_free( &aliases ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "control-flow code produced" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + + __cpu_pc = 0; + CHECK( __cpu_decode( &insn ) == 0, "decode JMP" ); + CHECK( insn.opcode == CPU_OP_JMP, "first opcode JMP" ); + CHECK( insn.length == 10, "JMP length" ); + CHECK( insn.operand[0].type == CPU_OPERAND_CODE64, "JMP CODE64 operand" ); + CHECK( insn.operand[0].value.code64 < im.code_size, "JMP target in PROGRAM" ); + + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_SUCCESS, "control-flow run" ); + CHECK( __cpu_pc == im.code_size, "PC after final HALT" ); + CHECK( __cpu_gth() != 0, "HALT flag set" ); + + iatoi( ( mpu_int * ) zero, "0", NB_I64 ); + iatoi( ( mpu_int * ) forty_two, "42", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0 ), zero, NB_I64 ), "backward JNZ loop reached zero" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 24 ), forty_two, NB_I64 ), "forward JSET reached done label" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu control-flow regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-conversion-flags.c b/tests/tst-conversion-flags.c new file mode 100644 index 0000000..5244c1a --- /dev/null +++ b/tests/tst-conversion-flags.c @@ -0,0 +1,204 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +static int failures; +#define CHECK(c,m) do { if(!(c)){ fprintf(stderr,"FAIL: %s\n",(m)); ++failures; } } while(0) + +static cpu_operand +R( unsigned bits, __mpu_uint32_t addr ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = addr; + return( o ); +} + +static cpu_operand +IMM( __mpu_uint64_t v ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_IMM64; + o.value.imm64 = v; + return( o ); +} + +static void +I( cpu_instruction *i, cpu_opcode op ) +{ + memset( i, 0, sizeof( *i ) ); + i->opcode = op; +} + +static __mpu_UNIT_t * +P( unsigned bits, __mpu_uint32_t addr ) +{ + return( __cpu_reg_ptr( bits, addr ) ); +} + +static __mpu_uint32_t +seed_psw( void ) +{ + __mpu_uint32_t p= + PSW_AF_MASK | PSW_OF_MASK | PSW_PF_MASK | PSW_RF_MASK | + PSW_DOMF_MASK | PSW_OVFF_MASK | PSW_TLSF_MASK | PSW_INDF_MASK | + PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)2U << PSW_MODE_SHIFT ); + __cpu_set_psw( p ); + return( p ); +} + +static void +preserved( const char *name ) +{ + __mpu_uint32_t expect = seed_psw(); + (void)name; + CHECK( __cpu_get_psw() == expect, "PSW seed" ); +} + +static void +check_after( const char *name, __mpu_uint32_t expect ) +{ + if( __cpu_get_psw() != expect ) + { + fprintf( stderr, "FAIL: %s changed PSW: got 0x%08x expected 0x%08x\n", + name, (unsigned)__cpu_get_psw(), (unsigned)expect); + ++failures; + } +} + +int +main( void ) +{ + cpu_instruction x; + __mpu_uint32_t psw; + __cpu_init(); + + /* LDI: runtime icvt is an implementation detail and must be flag-neutral. */ + I( &x, CPU_OP_LDI ); + x.operand[0] = R( 128, 0x000 ); + x.operand[1] = IMM( 42 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDI exec" ); + check_after( "LDI", psw ); + + /* LDR: runtime real64 -> destination conversion is also flag-neutral. */ + { + __mpu_real64_t v = 2.0; + __mpu_uint64_t bits; + memcpy( &bits, &v, 8 ); + I( &x, CPU_OP_LDR ); + x.operand[0] = R( 128, 0x020 ); + x.operand[1] = IMM( bits ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDR exec" ); + check_after( "LDR", psw ); + } + + /* LDC: two real32 components are widened internally, but PSW is preserved. */ + { + __mpu_real32_t vr = 1.0f, vi = 2.0f; + __mpu_uint32_t br, bi; + __mpu_uint64_t payload; + memcpy( &br, &vr, 4 ); + memcpy( &bi, &vi, 4 ); + payload = ( (__mpu_uint64_t)br << 32 ) | bi; + I( &x, CPU_OP_LDC ); + x.operand[0] = R( 256, 0x040 ); + x.operand[1] = IMM( payload ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDC exec" ); + check_after( "LDC", psw ); + } + + /* Explicit integer conversion. */ + *P( 8, 0x080 ) = 0xff; + I( &x, CPU_OP_ICVT ); + x.operand[0] = R( 16, 0x082 ); + x.operand[1] = R( 8, 0x080 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "ICVT exec" ); + check_after( "ICVT", psw ); + + /* Explicit Real conversion. */ + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x090 ), (__mpu_char8_t *)"3.5", NB_R64 ) >= 0, "real source" ); + I( &x, CPU_OP_RCVT ); + x.operand[0] = R( 128, 0x0a0 ); + x.operand[1] = R( 64, 0x090 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RCVT exec" ); + check_after( "RCVT", psw ); + + /* Integer <-> Real conversions. */ + __cpu_data_put_u64( P( 64, 0x0c0 ), 42 ); + I( &x, CPU_OP_ITOR ); + x.operand[0] = R( 64, 0x0c8 ); + x.operand[1] = R( 64, 0x0c0 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "ITOR exec" ); + check_after( "ITOR", psw ); + + __cpu_data_put_u64( P( 64, 0x0d0 ), 42 ); + I( &x, CPU_OP_UITOR ); + x.operand[0] = R( 128, 0x0e0 ); + x.operand[1] = R( 64, 0x0d0 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "UITOR exec" ); + check_after( "UITOR", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x100 ), (__mpu_char8_t *)"42.5", NB_R64 ) >= 0, "RTOI source" ); + I( &x, CPU_OP_RTOI ); + x.operand[0] = R( 64, 0x108 ); + x.operand[1] = R( 64, 0x110 ); + x.operand[2] = R( 64, 0x100 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RTOI exec" ); + check_after( "RTOI", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x118 ), (__mpu_char8_t *)"42.5", NB_R64 ) >= 0, "RTOUI source" ); + I( &x, CPU_OP_RTOUI ); + x.operand[0] = R( 64, 0x120 ); + x.operand[1] = R( 64, 0x128 ); + x.operand[2] = R( 64, 0x118 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RTOUI exec" ); + check_after( "RTOUI", psw ); + + /* Complex conversion and Real -> Complex construction. */ + { + mpu_real64_t rr, ri; + CHECK( ascii_to_real( ( mpu_real * ) rr, (__mpu_char8_t *)"3.0", NB_R64 ) >= 0, "complex real" ); + CHECK( ascii_to_real( ( mpu_real * ) ri, (__mpu_char8_t *)"4.0", NB_R64 ) >= 0, "complex imag" ); + c_gen_complex( ( mpu_complex * ) P( 128, 0x140 ), ( mpu_real * ) rr, ( mpu_real * ) ri, NB_R64 ); + } + I( &x, CPU_OP_CCVT ); + x.operand[0] = R( 256, 0x160 ); + x.operand[1] = R( 128, 0x140 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "CCVT exec" ); + check_after( "CCVT", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x190 ), (__mpu_char8_t *)"5.0", NB_R64 ) >= 0, "CRTOC source" ); + I( &x, CPU_OP_CRTOC ); + x.operand[0] = R( 128, 0x1a0 ); + x.operand[1] = R( 64, 0x190 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "CRTOC exec" ); + check_after( "CRTOC", psw ); + + __cpu_free_context(); + if( failures ) + { + fprintf( stderr, "libcpu conversion flag regression: %d failure(s)\n", failures ); + return( 1 ); + } + puts( "libcpu conversion flag preservation regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-doc-version-policy.sh b/tests/tst-doc-version-policy.sh new file mode 100755 index 0000000..cd50995 --- /dev/null +++ b/tests/tst-doc-version-policy.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +: "${TEST_SRCDIR:?}" + +TOP_SRCDIR=`cd "${TEST_SRCDIR}/.." && pwd` +README="${TOP_SRCDIR}/README.md" +MAN_DIR="${TOP_SRCDIR}/man" + +fail() +{ + echo "tst-doc-version-policy: $*" >&2 + exit 1 +} + +# README is a current-interface document, not a release changelog. Keep +# package release numbers in NEWS/ChangeLog and obtain the installed version +# from the tools themselves. +if grep -E 'LibMCPU [0-9]+\.[0-9]+\.[0-9]+|libmcpu-[0-9]+\.[0-9]+\.[0-9]+' \ + "${README}" >/dev/null ; then + fail "README.md contains a release-bound LibMCPU version string" +fi + +# Manual pages describe the interface contract. Their .TH source field is +# deliberately version-independent so a release bump cannot leave stale +# numbers behind. +while IFS= read -r page ; do + header=`sed -n '1p' "${page}"` + case "${header}" in + *'"LibMCPU"'*) + ;; + *) + fail "${page}: .TH source field is not version-independent" + ;; + esac + +done < <(find "${MAN_DIR}" -type f \( -name '*.1' -o -name '*.7' \) | sort) + +if grep -ER 'LibMCPU [0-9]+\.[0-9]+\.[0-9]+' "${MAN_DIR}" >/dev/null ; then + fail "manual page contains a release-bound LibMCPU version string" +fi + +exit 0 diff --git a/tests/tst-dup.c b/tests/tst-dup.c new file mode 100644 index 0000000..3941378 --- /dev/null +++ b/tests/tst-dup.c @@ -0,0 +1,187 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> +#include <sys/asm/errno.h> +#include <sys/asm/fcntl.h> +#include <sys/asm/syscall.h> + +#include "mcpu-asm.h" + + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +static void +put32( __mpu_uint32_t address, __mpu_uint32_t value ) +{ + __cpu_data_put_u32( __cpu_reg_ptr( 32, address ), value ); +} + + +static void +put64( __mpu_uint32_t address, __mpu_uint64_t value ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), value ); +} + + +static __mpu_uint64_t +reg64( __mpu_uint32_t address ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); +} + + +static int +run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno, + __mpu_uint64_t arg0, __mpu_uint64_t arg1, + __mpu_uint64_t arg2, __mpu_uint64_t *ret ) +{ + int rc; + + put32( CPU_SYSCALL_NARGS_ADDR, nargs ); + put32( CPU_SYSCALL_SYSNO_ADDR, sysno ); + put64( CPU_SYSCALL_ARG0_ADDR, arg0 ); + put64( CPU_SYSCALL_ARG1_ADDR, arg1 ); + put64( CPU_SYSCALL_ARG2_ADDR, arg2 ); + + __cpu_pc = 0; + rc = __cpu_run(); + if( rc != CPU_EXEC_SUCCESS ) return( rc ); + + *ret = reg64( CPU_SYSCALL_RET_ADDR ); + return( 0 ); + +} /* End of run_syscall() */ + + +static __mpu_int64_t +signed_result( __mpu_uint64_t value ) +{ + __mpu_int64_t ret; + + memcpy( &ret, &value, sizeof( ret ) ); + return( ret ); + +} /* End of signed_result() */ + + +int +main( void ) +{ + static const char source[] = + ".text\n" + "SYSCALL\n" + "HALT\n"; + cpu_asm_image image; + char err[256]; + __mpu_uint64_t ret; + + memset( &image, 0, sizeof( image ) ); + + CHECK( __NR_dup == 0x126, "public __NR_dup ABI number" ); + CHECK( __NR_dup2 == 0x127, "public __NR_dup2 ABI number" ); + + __cpu_init(); + + CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( image.code, image.code_size ) == 0, + "program load" ); + + /* dup() returns the lowest free guest descriptor. */ + CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, "dup stdout" ); + CHECK( ret == 3, "dup returns lowest free guest fd 3" ); + + /* Descriptor flags are not copied by dup(). */ + CHECK( run_syscall( 3, __NR_fcntl, 1, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set FD_CLOEXEC on source" ); + CHECK( ret == 0, "F_SETFD source result" ); + CHECK( run_syscall( 3, __NR_fcntl, 3, F_GETFD, 0, &ret ) == 0, + "F_GETFD duplicated descriptor" ); + CHECK( ret == 0, "dup clears descriptor flags" ); + + /* dup2() installs exactly the requested guest descriptor. */ + CHECK( run_syscall( 2, __NR_dup2, 1, 10, 0, &ret ) == 0, + "dup2 stdout to guest fd 10" ); + CHECK( ret == 10, "dup2 returns requested guest fd" ); + + CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set FD_CLOEXEC on dup2 target" ); + CHECK( ret == 0, "set target flags" ); + + /* Replacing an occupied target clears descriptor flags. */ + CHECK( run_syscall( 2, __NR_dup2, 3, 10, 0, &ret ) == 0, + "dup2 replaces occupied target" ); + CHECK( ret == 10, "dup2 replacement return" ); + CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, + "F_GETFD replaced target" ); + CHECK( ret == 0, "dup2 replacement clears descriptor flags" ); + + /* oldfd == newfd is a no-op and preserves descriptor flags. */ + CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, + "F_GETFD source before dup2 self" ); + CHECK( ret == FD_CLOEXEC, "source flag before dup2 self" ); + CHECK( run_syscall( 2, __NR_dup2, 1, 1, 0, &ret ) == 0, + "dup2 oldfd equals newfd" ); + CHECK( ret == 1, "dup2 self return" ); + CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, + "F_GETFD source after dup2 self" ); + CHECK( ret == FD_CLOEXEC, "dup2 self preserves descriptor flags" ); + + /* Closing a borrowed standard guest fd does not close HOST stdio, and dup() + may subsequently reuse that lowest free guest slot. */ + CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, + "close borrowed guest stdin" ); + CHECK( ret == 0, "close borrowed guest stdin result" ); + CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, + "dup reuses closed guest fd 0" ); + CHECK( ret == 0, "dup returns lowest free guest fd 0" ); + + /* Invalid source and target descriptors use the stable MCPU EBADF value. */ + CHECK( run_syscall( 1, __NR_dup, 255, 0, 0, &ret ) == 0, + "dup invalid source" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup invalid source returns -EBADF" ); + + CHECK( run_syscall( 2, __NR_dup2, 1, 256, 0, &ret ) == 0, + "dup2 invalid target" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup2 invalid target returns -EBADF" ); + + CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set target flag before invalid-source dup2" ); + CHECK( run_syscall( 2, __NR_dup2, 255, 10, 0, &ret ) == 0, + "dup2 invalid source with occupied target" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup2 invalid source returns -EBADF" ); + CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, + "target survives invalid-source dup2" ); + CHECK( ret == FD_CLOEXEC, + "invalid-source dup2 leaves target unchanged" ); + + CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, + "close reused guest fd 0" ); + CHECK( run_syscall( 1, __NR_close, 3, 0, 0, &ret ) == 0, + "close dup guest fd 3" ); + CHECK( run_syscall( 1, __NR_close, 10, 0, 0, &ret ) == 0, + "close dup2 guest fd 10" ); + + cpu_asm_image_free( &image ); + __cpu_free_context(); + + puts( "libmcpu dup/dup2 regression: PASS" ); + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-dynamic-runner.sh b/tests/tst-dynamic-runner.sh new file mode 100755 index 0000000..2ebc76f --- /dev/null +++ b/tests/tst-dynamic-runner.sh @@ -0,0 +1,96 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +TMP=${TMPDIR:-/tmp}/libcpu-dynamic-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/a.o" "$EXAMPLES/dynamic-liba.s" +"$CPU_AS" -o "$TMP/b.o" "$EXAMPLES/dynamic-libb.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/main-program" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/libb.so" "$TMP/b.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/main-program"`" = 41 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/liba.so" "$TMP/main-program"`" = 42 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/liba.so:$TMP/libb.so" "$TMP/main-program"`" = 43 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/libb.so:$TMP/liba.so" "$TMP/main-program"`" = 42 ] + +# LOCAL references inside ET_DYN are private to that module, but their +# section-relative values still need startup rebasing by the module base. +# local_helper is deliberately not exported. Without LOCAL ET_DYN runtime +# relocation the CALL below contains the shared-object-relative offset and +# jumps to the wrong process PROGRAM address after the library is placed +# behind the executable. +cat > "$TMP/local-main.s" <<'EOS' +.global _life +.extern local_api +.text +_start_life: + LDI r64@0x00000, 40 + CALL local_api + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/local-lib.s" <<'EOS' +.global local_api +.text +local_api: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 7 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/local-main.o" "$TMP/local-main.s" +"$CPU_AS" -o "$TMP/local-lib.o" "$TMP/local-lib.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/local-program" "$TMP/local-main.o" +"$CPU_LD" --shared --soname=local.so.1 -o "$TMP/local.so" "$TMP/local-lib.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/local.so" "$TMP/local-program"`" = 47 ] + +# ABI-v1 static executable must reject preload rather than silently relink. +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/static-program" "$TMP/main.o" +if "$CPU_RUN" --ldpreload="$TMP/liba.so" "$TMP/static-program" >/dev/null 2>&1; then + echo "ABI-v1 executable unexpectedly accepted --ldpreload" >&2 + exit 1 +fi + +# An ABI-v3 executable with an unresolved import but no DT_NEEDED provider +# remains a startup load error. Recursive loading cannot invent dependencies. +cat > "$TMP/missing.s" <<'EOS' +.global _life +.extern missing_function +.text +_start_life: + CALL missing_function + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/missing.o" "$TMP/missing.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/missing-program" "$TMP/missing.o" +if "$CPU_RUN" "$TMP/missing-program" >/dev/null 2>&1; then + echo "unresolved dynamic symbol unexpectedly reached execution" >&2 + exit 1 +fi + +# Shared objects own no process STACK/HEAP policy. +if "$CPU_LD" --shared --soname=bad.so.1 --stack-size=4096 -o "$TMP/bad.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "cpu-ld --shared unexpectedly accepted --stack-size" >&2 + exit 1 +fi +if "$CPU_LD" --shared -o "$TMP/no-soname.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "ABI-v3 shared object unexpectedly accepted without SONAME" >&2 + exit 1 +fi +if "$CPU_LD" --shared --soname=path/bad.so.1 -o "$TMP/bad-soname.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "ABI-v3 shared object unexpectedly accepted pathname SONAME" >&2 + exit 1 +fi +if "$CPU_LD" --dynamic --soname=main.so.1 --stack-size=4096 --no-heap \ + -o "$TMP/bad-main" "$TMP/main.o" >/dev/null 2>&1; then + echo "ABI-v3 executable unexpectedly accepted SONAME" >&2 + exit 1 +fi +exit 0 diff --git a/tests/tst-dynamic.c b/tests/tst-dynamic.c new file mode 100644 index 0000000..a70d9e1 --- /dev/null +++ b/tests/tst-dynamic.c @@ -0,0 +1,407 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libmcpu.h> + +#include "mcpu-asm.h" +#include "mcpu-link.h" +#include "mcpu-dyn.h" + +static int +add_symbol( cpu_asm_object *o, const char *name, unsigned sec, unsigned bind, unsigned type, __mpu_uint64_t value ) +{ + cpu_asm_symbol *q = (cpu_asm_symbol *)realloc( o->symbols, ( o->symbol_count + 1 ) * sizeof( *q ) ); + if( !q ) return( -1 ); + o->symbols = q; + memset( &o->symbols[o->symbol_count], 0, sizeof( *q ) ); + o->symbols[o->symbol_count].name = strdup( name ); + if( !o->symbols[o->symbol_count].name ) return( -1 ); + o->symbols[o->symbol_count].section = sec; + o->symbols[o->symbol_count].binding = bind; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + ++o->symbol_count; + return( 0 ); +} + +static int +add_reloc( cpu_asm_object *o, unsigned sec, unsigned type, size_t sym, __mpu_uint64_t off ) +{ + cpu_asm_reloc *q = (cpu_asm_reloc *)realloc( o->relocs, ( o->reloc_count + 1 ) * sizeof( *q ) ); + if( !q ) return( -1 ); + o->relocs = q; + o->relocs[o->reloc_count] = ( cpu_asm_reloc ) + { + sec, type, sym, off, 0 + }; + ++o->reloc_count; + return( 0 ); +} + +static void +init_text( cpu_asm_object *o, size_t n ) +{ + memset( o, 0, sizeof( *o ) ); + o->text = (__mpu_UNIT_t *)calloc( n, 1 ); + o->text_size = n; + o->text_align = 1; + o->has_text = 1; +} + +static int +tempname( char *buf, size_t n, const char *tag ) +{ + int fd; + if( snprintf( buf, n, "/tmp/libcpu-%s-XXXXXX", tag ) < 0 ) return( -1 ); + fd = mkstemp( buf ); + if( fd < 0 ) return( -1 ); + close( fd ); + return( 0 ); +} + +int +main( void ) +{ + cpu_asm_object mo = + { + 0 + } + , ao = + { + 0 + } + , bo = + { + 0 + } + , wo = + { + 0 + } + , co = + { + 0 + }; + cpu_asm_image mi = + { + 0 + } + , ai = + { + 0 + } + , bi = + { + 0 + } + , wi = + { + 0 + } + , ci = + { + 0 + } + , si = + { + 0 + }; + cpu_exec_options opt = + { + 256, 0, 0, 0 + }; + cpu_exec_loaded ld; + const char *needed_a[] = + { + "liba.so.1" + }; + cpu_dyn_metadata v3_main = + { + 0, 0, 0 + }; + cpu_dyn_metadata v3_liba = + { + "liba.so.1", 0, 0 + }; + cpu_dyn_metadata v3_needed = + { + 0, needed_a, 1 + }; + char me[128], ae[128], be[128], we[128], ce[128]; + char v3me[128], v3ae[128], v3ne[128], pre[520], err[512]; + char *av[] = + { + "main-program" + }; + int rc = 1; + __mpu_init(); + init_text( &mo, 24 ); + init_text( &ao, 16 ); + init_text( &bo, 16 ); + init_text( &wo, 16 ); + init_text( &co, 16 ); + ao.data = (__mpu_UNIT_t *)calloc( 16, 1 ); + ao.data_size = 16; + ao.data_align = 8; + ao.has_data = 1; + if( !mo.text || !ao.text || !ao.data || !bo.text || !wo.text || !co.text ) goto out; + if( add_symbol( &mo, "_start", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_symbol( &mo, "_life", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_symbol( &mo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 16 ) || + add_symbol( &mo, "shared_data", CPU_OBJ_SEC_UNDEF, CPU_OBJ_BIND_WEAK, CPU_OBJ_TYPE_OBJECT, 0 ) || + add_reloc( &mo, CPU_OBJ_SEC_TEXT, CPU_RELOC_PTR64_CODE, 3, 0 ) || + add_reloc( &mo, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 2, 8 ) || + add_symbol( &ao, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 4 ) || + add_symbol( &ao, "shared_data", CPU_OBJ_SEC_DATA, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_OBJECT, 0 ) || + add_symbol( &ao, "local_code", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_LOCAL, CPU_OBJ_TYPE_FUNC, 12 ) || + add_reloc( &ao, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 2, 0 ) || + add_symbol( &ao, "local_data", CPU_OBJ_SEC_DATA, CPU_OBJ_BIND_LOCAL, CPU_OBJ_TYPE_OBJECT, 8 ) || + add_reloc( &ao, CPU_OBJ_SEC_DATA, CPU_RELOC_PTR64_DATA, 3, 0 ) || + add_symbol( &bo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 6 ) || + add_symbol( &bo, "helper", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 10 ) || + add_symbol( &wo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_WEAK, CPU_OBJ_TYPE_FUNC, 8 ) || + add_symbol( &co, "helper", CPU_OBJ_SEC_UNDEF, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_reloc( &co, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 0, 0 )) goto out; + if( cpu_link_objects( &mo, 1, &si, err, sizeof( err ) ) ) + { + fprintf( stderr, "static compatibility link: %s\n", err ); + goto out; + } + if( si.dyn_symbol_count || si.dyn_reloc_count || __cpu_code_get_u64( si.code ) != 0 || __cpu_code_get_u64( si.code + 8 ) != 16 ) + { + fprintf( stderr, "ABI-v1 static link compatibility mismatch\n" ); + goto out; + } + if( cpu_link_objects_mode( &mo, 1, &mi, CPU_LINK_DYNAMIC_EXEC, err, sizeof( err ) ) ) + { + fprintf( stderr, "main link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &ao, 1, &ai, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "a link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &bo, 1, &bi, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "b link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &wo, 1, &wi, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "weak link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &co, 1, &ci, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "cross link: %s\n", err ); + goto out; + } + if( mi.dyn_reloc_count != 2 || mi.dyn_symbol_count != 2 ) + { + fprintf( stderr, "dynamic metadata count mismatch\n" ); + goto out; + } + if( ai.dyn_reloc_count != 2 || ai.dyn_symbol_count != 4 ) + { + fprintf( stderr, "LOCAL ET_DYN metadata count mismatch\n" ); + goto out; + } + if( tempname( me, sizeof( me ), "main" ) || + tempname( ae, sizeof( ae ), "a" ) || + tempname( be, sizeof( be ), "b" ) || + tempname( we, sizeof( we ), "weak" ) || + tempname( ce, sizeof( ce ), "cross" ) || + tempname( v3me, sizeof( v3me ), "v3-main" ) || + tempname( v3ae, sizeof( v3ae ), "v3-a" ) || + tempname( v3ne, sizeof( v3ne ), "v3-needed" ) ) goto out; + if( cpu_dyn_write( me, &mi, &opt, 0, err, sizeof( err ) ) || + cpu_dyn_write( ae, &ai, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( be, &bi, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( we, &wi, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( ce, &ci, 0, 1, err, sizeof( err ) ) ) + { + fprintf( stderr, "write ABI-v2: %s\n", err ); + goto files; + } + if( cpu_dyn_write_abi3(v3me, &mi, &opt, 0, &v3_main, + err, sizeof( err )) || + cpu_dyn_write_abi3(v3ae, &ai, 0, 1, &v3_liba, + err, sizeof( err )) || + cpu_dyn_write_abi3(v3ne, &mi, &opt, 0, &v3_needed, + err, sizeof( err )) ) + { + fprintf( stderr, "write ABI-v3: %s\n", err ); + goto files; + } + __cpu_init(); + if( cpu_dyn_load( me, NULL, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "load main: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 16 || __cpu_code_get_u64( __cpu_program_ptr() ) != 0 ) + { + fprintf( stderr, "main/weak definition mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + snprintf( pre, sizeof( pre ), "%s:%s", ae, be ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "load: %s\n", err ); + __cpu_free_context(); + goto files; + } + /* main PROGRAM=24, a=16, b=16; later strong b wins: 24+16+6 = 46. */ + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 46 ) + { + fprintf( stderr, "interposition mismatch: got %llu\n", (unsigned long long)__cpu_code_get_u64( __cpu_program_ptr() + 8 ) ); + __cpu_free_context(); + goto files; + } + /* main has no DATA; loader creates the single process guard and places + liba DATA at offset 16. ET_DYN itself contains no per-library guard. */ + if( __cpu_code_get_u64( __cpu_program_ptr() ) != 16 ) + { + fprintf( stderr, "shared DATA base/guard mismatch\n" ); + __cpu_free_context(); + goto files; + } + /* LOCAL ET_DYN references are not interposable, but they still require + startup rebasing by the final module PROGRAM/DATA base. */ + if( __cpu_code_get_u64( __cpu_program_ptr() + 24 ) != 36 ) + { + fprintf( stderr, "LOCAL ET_DYN CODE64 rebase mismatch\n" ); + __cpu_free_context(); + goto files; + } + { + __mpu_uint64_t p = 0; + if( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 24 ) || __cpu_data_get_u64( __cpu_data_ptr() + 16 ) != p ) + { + fprintf( stderr, "LOCAL ET_DYN PTR64_DATA rebase mismatch\n" ); + __cpu_free_context(); + goto files; + } + } + __cpu_free_context(); + + /* A shared object can import from another explicitly preloaded object + without DT_NEEDED. cross is placed at 24, b at 40, helper at 40+10. */ + snprintf( pre, sizeof( pre ), "%s:%s", ce, be ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "cross-library load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 24 ) != 50 ) + { + fprintf( stderr, "cross-library explicit-preload resolution mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* A later WEAK never preempts an earlier strong GLOBAL, including main. */ + __cpu_init(); + if( cpu_dyn_load( me, we, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "weak load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 16 ) + { + fprintf( stderr, "weak incorrectly preempted main GLOBAL\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* Strong library a beats later weak: a base=24, foo=4 => 28. */ + snprintf( pre, sizeof( pre ), "%s:%s", ae, we ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "strong/weak load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 28 ) + { + fprintf( stderr, "strong/weak binding mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* ABI-v3 without NEEDED keeps the established startup-relocation + semantics and can still use the explicit preload compatibility path. */ + __cpu_init(); + if( cpu_dyn_load( v3me, v3ae, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "ABI-v3 explicit-preload load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 28 ) + { + fprintf( stderr, "ABI-v3 explicit-preload binding mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* The low-level loader still refuses to ignore unresolved DT_NEEDED. + mcpu-run first resolves the graph and then uses cpu_dyn_load_resolved(). */ + __cpu_init(); + if( cpu_dyn_load( v3ne, NULL, 1, av, &ld, err, sizeof( err ) ) == 0 ) + { + fprintf( stderr, "low-level ABI-v3 DT_NEEDED unexpectedly ignored\n" ); + __cpu_free_context(); + goto files; + } + if( strstr( err, "DT_NEEDED loading is not implemented yet" ) == NULL ) + { + fprintf( stderr, "unexpected ABI-v3 DT_NEEDED diagnostic: %s\n", err ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* A static ABI-v1 executable must not silently accept preload; this is + checked by cpu_exec_load_preload runner tests. Here verify no missing + explicit library dependency machinery is needed: the list alone resolves. */ + printf( "libcpu ABI-v2 compatibility / ABI-v3 metadata regression: PASS\n" ); + rc = 0; +files: unlink( me ); +unlink( ae ); +unlink( be ); +unlink( we ); +unlink( ce ); +unlink( v3me ); +unlink( v3ae ); +unlink( v3ne ); +out: cpu_asm_image_free( &si ); +cpu_asm_image_free( &mi ); +cpu_asm_image_free( &ai ); +cpu_asm_image_free( &bi ); +cpu_asm_image_free( &wi ); +cpu_asm_image_free( &ci ); +cpu_asm_object_free( &mo ); +cpu_asm_object_free( &ao ); +cpu_asm_object_free( &bo ); +cpu_asm_object_free( &wo ); +cpu_asm_object_free( &co ); +__mpu_free_context(); +return( rc ); +} diff --git a/tests/tst-env-preload-runner.sh b/tests/tst-env-preload-runner.sh new file mode 100755 index 0000000..5f49e9b --- /dev/null +++ b/tests/tst-env-preload-runner.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-env-preload-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.global foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/lib/libb.so.1" "$TMP/b.o" +ln -s liba.so.1 "$TMP/lib/liba.so" +ln -s libb.so.1 "$TMP/lib/libb.so" + +# Empty environment is the normal no-preload baseline. +[ "`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 41 ] + +# Environment-only preload uses the same resolver and rightmost-wins ordering. +[ "`MCPU_LD_PRELOAD=liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] +[ "`MCPU_LD_PRELOAD=liba.so:libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] +[ "`MCPU_LD_PRELOAD=libb.so:liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Command-line preload entries are appended after environment entries, so the +# established rightmost-higher-priority rule makes command line stronger. +[ "`MCPU_LD_PRELOAD=liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] +[ "`MCPU_LD_PRELOAD=libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=liba.so --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] +[ "`MCPU_LD_PRELOAD=liba.so:libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so:liba.so --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Environment preload is equivalent to --ldpreload for ABI compatibility: an +# ABI-v1 static executable must reject it just as it rejects the CLI option. +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/static" "$TMP/main.o" +if MCPU_LD_PRELOAD="$TMP/lib/liba.so.1" "$CPU_RUN" "$TMP/static" \ + >/dev/null 2>"$TMP/static.err"; then + echo "ABI-v1 executable unexpectedly accepted MCPU_LD_PRELOAD" >&2 + exit 1 +fi +grep -q 'dynamic modules require' "$TMP/static.err" + +# The environment preload is independent of configuration suppression. An +# explicit pathname therefore still works under --no-config. +[ "`MCPU_LD_PRELOAD="$TMP/lib/liba.so.1" "$CPU_RUN" --no-config \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Environment preload remains an explicit override above a normal dependency. +cat > "$TMP/import.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/import.o" "$TMP/import.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -L"$TMP/lib" -la \ + -o "$TMP/import" "$TMP/import.o" +[ "`MCPU_LD_PRELOAD=libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/import"`" = 43 ] + +# Invalid environment lists and missing names are hard errors. +if MCPU_LD_PRELOAD=liba.so::libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + "$TMP/main" >/dev/null 2>"$TMP/bad-list.err"; then + echo "malformed MCPU_LD_PRELOAD unexpectedly accepted" >&2 + exit 1 +fi +grep -q 'MCPU_LD_PRELOAD' "$TMP/bad-list.err" + +if MCPU_LD_PRELOAD=does-not-exist.so "$CPU_RUN" --library-path="$TMP/lib" \ + "$TMP/main" >/dev/null 2>"$TMP/missing.err"; then + echo "missing MCPU_LD_PRELOAD library unexpectedly accepted" >&2 + exit 1 +fi +grep -q 'MCPU_LD_PRELOAD' "$TMP/missing.err" + +MCPU_LD_PRELOAD= "$CPU_RUN" --help 2>&1 | grep -q 'MCPU_LD_PRELOAD' + +exit 0 diff --git a/tests/tst-errno-abi.c b/tests/tst-errno-abi.c new file mode 100644 index 0000000..1d7dcd5 --- /dev/null +++ b/tests/tst-errno-abi.c @@ -0,0 +1,217 @@ +#include <stdio.h> + +#include <libmpu.h> + +/* Capture the LibMPU values before the public assembler header is included. */ +enum +{ + LIBMPU_EUNKNOWN = __EUNKNOWN__, + LIBMPU_I_ESIZE = __I_ESIZE__, + LIBMPU_I_ESHIFT = __I_ESHIFT__, + LIBMPU_I_ENUMBER = __I_ENUMBER__, + LIBMPU_I_ERADIX = __I_ERADIX__, + LIBMPU_I_MAX_ERRNO = __I_MAX_ERRNO, + LIBMPU_R_ESIZE = __R_ESIZE__, + LIBMPU_R_ETRUNC = __R_ETRUNC__, + LIBMPU_R_MAX_ERRNO = __R_MAX_ERRNO, + LIBMPU_C_EDIVZEROBYZERO = __C_EDIVZEROBYZERO__, + LIBMPU_C_ELOGOFZERO = __C_ELOGOFZERO__, + LIBMPU_C_EPOWOFZERO = __C_EPOWOFZERO__, + LIBMPU_C_MAX_ERRNO = __C_MAX_ERRNO, + LIBMPU_DOMAIN = __DOMAIN__, + LIBMPU_SING = __SING__, + LIBMPU_OVERFLOW = __OVERFLOW__, + LIBMPU_UNDERFLOW = __UNDERFLOW__, + LIBMPU_TLOSS = __TLOSS__, + LIBMPU_PLOSS = __PLOSS__, + LIBMPU_INVALID = __INVALID__, + LIBMPU_INEXACT = __INEXACT__, + LIBMPU_M_MAX_ERRNO = __M_MAX_ERRNO +}; + +#include "../cpu/cpu-errno.h" +#include <sys/asm/errno.h> +#include <sys/asm/mpu-errno.h> + + +#define CHECK_SYSTEM_ERRNO(name) \ + do { \ + if( (name) != (__MCPU_ ## name) ) \ + { \ + fprintf( stderr, #name " ABI mismatch: public=%d internal=%d\n", \ + (int)(name), (int)(__MCPU_ ## name) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +int +main( void ) +{ + CHECK_SYSTEM_ERRNO( EPERM ); + CHECK_SYSTEM_ERRNO( ENOENT ); + CHECK_SYSTEM_ERRNO( ESRCH ); + CHECK_SYSTEM_ERRNO( EINTR ); + CHECK_SYSTEM_ERRNO( EIO ); + CHECK_SYSTEM_ERRNO( ENXIO ); + CHECK_SYSTEM_ERRNO( E2BIG ); + CHECK_SYSTEM_ERRNO( ENOEXEC ); + CHECK_SYSTEM_ERRNO( EBADF ); + CHECK_SYSTEM_ERRNO( ECHILD ); + CHECK_SYSTEM_ERRNO( EAGAIN ); + CHECK_SYSTEM_ERRNO( ENOMEM ); + CHECK_SYSTEM_ERRNO( EACCES ); + CHECK_SYSTEM_ERRNO( EFAULT ); + CHECK_SYSTEM_ERRNO( ENOTBLK ); + CHECK_SYSTEM_ERRNO( EBUSY ); + CHECK_SYSTEM_ERRNO( EEXIST ); + CHECK_SYSTEM_ERRNO( EXDEV ); + CHECK_SYSTEM_ERRNO( ENODEV ); + CHECK_SYSTEM_ERRNO( ENOTDIR ); + CHECK_SYSTEM_ERRNO( EISDIR ); + CHECK_SYSTEM_ERRNO( EINVAL ); + CHECK_SYSTEM_ERRNO( ENFILE ); + CHECK_SYSTEM_ERRNO( EMFILE ); + CHECK_SYSTEM_ERRNO( ENOTTY ); + CHECK_SYSTEM_ERRNO( ETXTBSY ); + CHECK_SYSTEM_ERRNO( EFBIG ); + CHECK_SYSTEM_ERRNO( ENOSPC ); + CHECK_SYSTEM_ERRNO( ESPIPE ); + CHECK_SYSTEM_ERRNO( EROFS ); + CHECK_SYSTEM_ERRNO( EMLINK ); + CHECK_SYSTEM_ERRNO( EPIPE ); + CHECK_SYSTEM_ERRNO( EDOM ); + CHECK_SYSTEM_ERRNO( ERANGE ); + CHECK_SYSTEM_ERRNO( EDEADLK ); + CHECK_SYSTEM_ERRNO( ENAMETOOLONG ); + CHECK_SYSTEM_ERRNO( ENOLCK ); + CHECK_SYSTEM_ERRNO( ENOSYS ); + CHECK_SYSTEM_ERRNO( ENOTEMPTY ); + CHECK_SYSTEM_ERRNO( ELOOP ); + CHECK_SYSTEM_ERRNO( ENOMSG ); + CHECK_SYSTEM_ERRNO( EIDRM ); + CHECK_SYSTEM_ERRNO( ECHRNG ); + CHECK_SYSTEM_ERRNO( EL2NSYNC ); + CHECK_SYSTEM_ERRNO( EL3HLT ); + CHECK_SYSTEM_ERRNO( EL3RST ); + CHECK_SYSTEM_ERRNO( ELNRNG ); + CHECK_SYSTEM_ERRNO( EUNATCH ); + CHECK_SYSTEM_ERRNO( ENOCSI ); + CHECK_SYSTEM_ERRNO( EL2HLT ); + CHECK_SYSTEM_ERRNO( EBADE ); + CHECK_SYSTEM_ERRNO( EBADR ); + CHECK_SYSTEM_ERRNO( EXFULL ); + CHECK_SYSTEM_ERRNO( ENOANO ); + CHECK_SYSTEM_ERRNO( EBADRQC ); + CHECK_SYSTEM_ERRNO( EBADSLT ); + CHECK_SYSTEM_ERRNO( EBFONT ); + CHECK_SYSTEM_ERRNO( ENOSTR ); + CHECK_SYSTEM_ERRNO( ENODATA ); + CHECK_SYSTEM_ERRNO( ETIME ); + CHECK_SYSTEM_ERRNO( ENOSR ); + CHECK_SYSTEM_ERRNO( ENONET ); + CHECK_SYSTEM_ERRNO( ENOPKG ); + CHECK_SYSTEM_ERRNO( EREMOTE ); + CHECK_SYSTEM_ERRNO( ENOLINK ); + CHECK_SYSTEM_ERRNO( EADV ); + CHECK_SYSTEM_ERRNO( ESRMNT ); + CHECK_SYSTEM_ERRNO( ECOMM ); + CHECK_SYSTEM_ERRNO( EPROTO ); + CHECK_SYSTEM_ERRNO( EMULTIHOP ); + CHECK_SYSTEM_ERRNO( EDOTDOT ); + CHECK_SYSTEM_ERRNO( EBADMSG ); + CHECK_SYSTEM_ERRNO( EOVERFLOW ); + CHECK_SYSTEM_ERRNO( ENOTUNIQ ); + CHECK_SYSTEM_ERRNO( EBADFD ); + CHECK_SYSTEM_ERRNO( EREMCHG ); + CHECK_SYSTEM_ERRNO( ELIBACC ); + CHECK_SYSTEM_ERRNO( ELIBBAD ); + CHECK_SYSTEM_ERRNO( ELIBSCN ); + CHECK_SYSTEM_ERRNO( ELIBMAX ); + CHECK_SYSTEM_ERRNO( ELIBEXEC ); + CHECK_SYSTEM_ERRNO( EILSEQ ); + CHECK_SYSTEM_ERRNO( ERESTART ); + CHECK_SYSTEM_ERRNO( ESTRPIPE ); + CHECK_SYSTEM_ERRNO( EUSERS ); + CHECK_SYSTEM_ERRNO( ENOTSOCK ); + CHECK_SYSTEM_ERRNO( EDESTADDRREQ ); + CHECK_SYSTEM_ERRNO( EMSGSIZE ); + CHECK_SYSTEM_ERRNO( EPROTOTYPE ); + CHECK_SYSTEM_ERRNO( ENOPROTOOPT ); + CHECK_SYSTEM_ERRNO( EPROTONOSUPPORT ); + CHECK_SYSTEM_ERRNO( ESOCKTNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EOPNOTSUPP ); + CHECK_SYSTEM_ERRNO( EPFNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EAFNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EADDRINUSE ); + CHECK_SYSTEM_ERRNO( EADDRNOTAVAIL ); + CHECK_SYSTEM_ERRNO( ENETDOWN ); + CHECK_SYSTEM_ERRNO( ENETUNREACH ); + CHECK_SYSTEM_ERRNO( ENETRESET ); + CHECK_SYSTEM_ERRNO( ECONNABORTED ); + CHECK_SYSTEM_ERRNO( ECONNRESET ); + CHECK_SYSTEM_ERRNO( ENOBUFS ); + CHECK_SYSTEM_ERRNO( EISCONN ); + CHECK_SYSTEM_ERRNO( ENOTCONN ); + CHECK_SYSTEM_ERRNO( ESHUTDOWN ); + CHECK_SYSTEM_ERRNO( ETOOMANYREFS ); + CHECK_SYSTEM_ERRNO( ETIMEDOUT ); + CHECK_SYSTEM_ERRNO( ECONNREFUSED ); + CHECK_SYSTEM_ERRNO( EHOSTDOWN ); + CHECK_SYSTEM_ERRNO( EHOSTUNREACH ); + CHECK_SYSTEM_ERRNO( EALREADY ); + CHECK_SYSTEM_ERRNO( EINPROGRESS ); + CHECK_SYSTEM_ERRNO( ESTALE ); + CHECK_SYSTEM_ERRNO( EUCLEAN ); + CHECK_SYSTEM_ERRNO( ENOTNAM ); + CHECK_SYSTEM_ERRNO( ENAVAIL ); + CHECK_SYSTEM_ERRNO( EISNAM ); + CHECK_SYSTEM_ERRNO( EREMOTEIO ); + CHECK_SYSTEM_ERRNO( EDQUOT ); + CHECK_SYSTEM_ERRNO( ENOMEDIUM ); + CHECK_SYSTEM_ERRNO( EMEDIUMTYPE ); + CHECK_SYSTEM_ERRNO( ECANCELED ); + CHECK_SYSTEM_ERRNO( ENOKEY ); + CHECK_SYSTEM_ERRNO( EKEYEXPIRED ); + CHECK_SYSTEM_ERRNO( EKEYREVOKED ); + CHECK_SYSTEM_ERRNO( EKEYREJECTED ); + CHECK_SYSTEM_ERRNO( EOWNERDEAD ); + CHECK_SYSTEM_ERRNO( ENOTRECOVERABLE ); + CHECK_SYSTEM_ERRNO( ERFKILL ); + CHECK_SYSTEM_ERRNO( EHWPOISON ); + CHECK_SYSTEM_ERRNO( EFTYPE ); + CHECK_SYSTEM_ERRNO( EUNKNOWN ); + + if( EWOULDBLOCK != EAGAIN || __MCPU_EWOULDBLOCK != __MCPU_EAGAIN ) + return( 1 ); + if( EDEADLOCK != EDEADLK || __MCPU_EDEADLOCK != __MCPU_EDEADLK ) + return( 1 ); + if( ENOTSUP != EOPNOTSUPP || __MCPU_ENOTSUP != __MCPU_EOPNOTSUPP ) + return( 1 ); + + if( __EUNKNOWN__ != LIBMPU_EUNKNOWN ) return( 1 ); + if( __I_ESIZE__ != LIBMPU_I_ESIZE || + __I_ESHIFT__ != LIBMPU_I_ESHIFT || + __I_ENUMBER__ != LIBMPU_I_ENUMBER || + __I_ERADIX__ != LIBMPU_I_ERADIX || + __I_MAX_ERRNO != LIBMPU_I_MAX_ERRNO ) + return( 1 ); + if( __R_ESIZE__ != LIBMPU_R_ESIZE || + __R_ETRUNC__ != LIBMPU_R_ETRUNC || + __R_MAX_ERRNO != LIBMPU_R_MAX_ERRNO ) + return( 1 ); + if( __C_EDIVZEROBYZERO__ != LIBMPU_C_EDIVZEROBYZERO || + __C_ELOGOFZERO__ != LIBMPU_C_ELOGOFZERO || + __C_EPOWOFZERO__ != LIBMPU_C_EPOWOFZERO || + __C_MAX_ERRNO != LIBMPU_C_MAX_ERRNO ) + return( 1 ); + if( __DOMAIN__ != LIBMPU_DOMAIN || __SING__ != LIBMPU_SING || + __OVERFLOW__ != LIBMPU_OVERFLOW || + __UNDERFLOW__ != LIBMPU_UNDERFLOW || __TLOSS__ != LIBMPU_TLOSS || + __PLOSS__ != LIBMPU_PLOSS || __INVALID__ != LIBMPU_INVALID || + __INEXACT__ != LIBMPU_INEXACT || __M_MAX_ERRNO != LIBMPU_M_MAX_ERRNO ) + return( 1 ); + + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-errno-map.c b/tests/tst-errno-map.c new file mode 100644 index 0000000..c8ab42d --- /dev/null +++ b/tests/tst-errno-map.c @@ -0,0 +1,439 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#include <libmcpu.h> +#include "../cpu/cpu-sysdeps.h" + + +#define CHECK_HOST_ERRNO(host_name, mcpu_name) \ + do { \ + int mapped = __cpu_errno_from_host( (host_name) ); \ + if( mapped != (__MCPU_ ## mcpu_name) ) \ + { \ + fprintf( stderr, #host_name " mapping mismatch: got=%d expected=%d\n", \ + mapped, (int)(__MCPU_ ## mcpu_name) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +int +main( void ) +{ + if( __cpu_errno_from_host( 0 ) != 0 ) return( 1 ); + if( __cpu_errno_from_host( -1 ) != __MCPU_EUNKNOWN ) return( 1 ); + if( __cpu_errno_from_host( 0x7fffffff ) != __MCPU_EUNKNOWN ) return( 1 ); + +#ifdef EPERM + CHECK_HOST_ERRNO( EPERM, EPERM ); +#endif +#ifdef ENOENT + CHECK_HOST_ERRNO( ENOENT, ENOENT ); +#endif +#ifdef ESRCH + CHECK_HOST_ERRNO( ESRCH, ESRCH ); +#endif +#ifdef EINTR + CHECK_HOST_ERRNO( EINTR, EINTR ); +#endif +#ifdef EIO + CHECK_HOST_ERRNO( EIO, EIO ); +#endif +#ifdef ENXIO + CHECK_HOST_ERRNO( ENXIO, ENXIO ); +#endif +#ifdef E2BIG + CHECK_HOST_ERRNO( E2BIG, E2BIG ); +#endif +#ifdef ENOEXEC + CHECK_HOST_ERRNO( ENOEXEC, ENOEXEC ); +#endif +#ifdef EBADF + CHECK_HOST_ERRNO( EBADF, EBADF ); +#endif +#ifdef ECHILD + CHECK_HOST_ERRNO( ECHILD, ECHILD ); +#endif +#ifdef EAGAIN + CHECK_HOST_ERRNO( EAGAIN, EAGAIN ); +#endif +#ifdef ENOMEM + CHECK_HOST_ERRNO( ENOMEM, ENOMEM ); +#endif +#ifdef EACCES + CHECK_HOST_ERRNO( EACCES, EACCES ); +#endif +#ifdef EFAULT + CHECK_HOST_ERRNO( EFAULT, EFAULT ); +#endif +#ifdef ENOTBLK + CHECK_HOST_ERRNO( ENOTBLK, ENOTBLK ); +#endif +#ifdef EBUSY + CHECK_HOST_ERRNO( EBUSY, EBUSY ); +#endif +#ifdef EEXIST + CHECK_HOST_ERRNO( EEXIST, EEXIST ); +#endif +#ifdef EXDEV + CHECK_HOST_ERRNO( EXDEV, EXDEV ); +#endif +#ifdef ENODEV + CHECK_HOST_ERRNO( ENODEV, ENODEV ); +#endif +#ifdef ENOTDIR + CHECK_HOST_ERRNO( ENOTDIR, ENOTDIR ); +#endif +#ifdef EISDIR + CHECK_HOST_ERRNO( EISDIR, EISDIR ); +#endif +#ifdef EINVAL + CHECK_HOST_ERRNO( EINVAL, EINVAL ); +#endif +#ifdef ENFILE + CHECK_HOST_ERRNO( ENFILE, ENFILE ); +#endif +#ifdef EMFILE + CHECK_HOST_ERRNO( EMFILE, EMFILE ); +#endif +#ifdef ENOTTY + CHECK_HOST_ERRNO( ENOTTY, ENOTTY ); +#endif +#ifdef ETXTBSY + CHECK_HOST_ERRNO( ETXTBSY, ETXTBSY ); +#endif +#ifdef EFBIG + CHECK_HOST_ERRNO( EFBIG, EFBIG ); +#endif +#ifdef ENOSPC + CHECK_HOST_ERRNO( ENOSPC, ENOSPC ); +#endif +#ifdef ESPIPE + CHECK_HOST_ERRNO( ESPIPE, ESPIPE ); +#endif +#ifdef EROFS + CHECK_HOST_ERRNO( EROFS, EROFS ); +#endif +#ifdef EMLINK + CHECK_HOST_ERRNO( EMLINK, EMLINK ); +#endif +#ifdef EPIPE + CHECK_HOST_ERRNO( EPIPE, EPIPE ); +#endif +#ifdef EDOM + CHECK_HOST_ERRNO( EDOM, EDOM ); +#endif +#ifdef ERANGE + CHECK_HOST_ERRNO( ERANGE, ERANGE ); +#endif +#ifdef EDEADLK + CHECK_HOST_ERRNO( EDEADLK, EDEADLK ); +#endif +#ifdef ENAMETOOLONG + CHECK_HOST_ERRNO( ENAMETOOLONG, ENAMETOOLONG ); +#endif +#ifdef ENOLCK + CHECK_HOST_ERRNO( ENOLCK, ENOLCK ); +#endif +#ifdef ENOSYS + CHECK_HOST_ERRNO( ENOSYS, ENOSYS ); +#endif +#ifdef ENOTEMPTY + CHECK_HOST_ERRNO( ENOTEMPTY, ENOTEMPTY ); +#endif +#ifdef ELOOP + CHECK_HOST_ERRNO( ELOOP, ELOOP ); +#endif +#ifdef ENOMSG + CHECK_HOST_ERRNO( ENOMSG, ENOMSG ); +#endif +#ifdef EIDRM + CHECK_HOST_ERRNO( EIDRM, EIDRM ); +#endif +#ifdef ECHRNG + CHECK_HOST_ERRNO( ECHRNG, ECHRNG ); +#endif +#ifdef EL2NSYNC + CHECK_HOST_ERRNO( EL2NSYNC, EL2NSYNC ); +#endif +#ifdef EL3HLT + CHECK_HOST_ERRNO( EL3HLT, EL3HLT ); +#endif +#ifdef EL3RST + CHECK_HOST_ERRNO( EL3RST, EL3RST ); +#endif +#ifdef ELNRNG + CHECK_HOST_ERRNO( ELNRNG, ELNRNG ); +#endif +#ifdef EUNATCH + CHECK_HOST_ERRNO( EUNATCH, EUNATCH ); +#endif +#ifdef ENOCSI + CHECK_HOST_ERRNO( ENOCSI, ENOCSI ); +#endif +#ifdef EL2HLT + CHECK_HOST_ERRNO( EL2HLT, EL2HLT ); +#endif +#ifdef EBADE + CHECK_HOST_ERRNO( EBADE, EBADE ); +#endif +#ifdef EBADR + CHECK_HOST_ERRNO( EBADR, EBADR ); +#endif +#ifdef EXFULL + CHECK_HOST_ERRNO( EXFULL, EXFULL ); +#endif +#ifdef ENOANO + CHECK_HOST_ERRNO( ENOANO, ENOANO ); +#endif +#ifdef EBADRQC + CHECK_HOST_ERRNO( EBADRQC, EBADRQC ); +#endif +#ifdef EBADSLT + CHECK_HOST_ERRNO( EBADSLT, EBADSLT ); +#endif +#ifdef EBFONT + CHECK_HOST_ERRNO( EBFONT, EBFONT ); +#endif +#ifdef ENOSTR + CHECK_HOST_ERRNO( ENOSTR, ENOSTR ); +#endif +#ifdef ENODATA + CHECK_HOST_ERRNO( ENODATA, ENODATA ); +#endif +#ifdef ETIME + CHECK_HOST_ERRNO( ETIME, ETIME ); +#endif +#ifdef ENOSR + CHECK_HOST_ERRNO( ENOSR, ENOSR ); +#endif +#ifdef ENONET + CHECK_HOST_ERRNO( ENONET, ENONET ); +#endif +#ifdef ENOPKG + CHECK_HOST_ERRNO( ENOPKG, ENOPKG ); +#endif +#ifdef EREMOTE + CHECK_HOST_ERRNO( EREMOTE, EREMOTE ); +#endif +#ifdef ENOLINK + CHECK_HOST_ERRNO( ENOLINK, ENOLINK ); +#endif +#ifdef EADV + CHECK_HOST_ERRNO( EADV, EADV ); +#endif +#ifdef ESRMNT + CHECK_HOST_ERRNO( ESRMNT, ESRMNT ); +#endif +#ifdef ECOMM + CHECK_HOST_ERRNO( ECOMM, ECOMM ); +#endif +#ifdef EPROTO + CHECK_HOST_ERRNO( EPROTO, EPROTO ); +#endif +#ifdef EMULTIHOP + CHECK_HOST_ERRNO( EMULTIHOP, EMULTIHOP ); +#endif +#ifdef EDOTDOT + CHECK_HOST_ERRNO( EDOTDOT, EDOTDOT ); +#endif +#ifdef EBADMSG + CHECK_HOST_ERRNO( EBADMSG, EBADMSG ); +#endif +#ifdef EOVERFLOW + CHECK_HOST_ERRNO( EOVERFLOW, EOVERFLOW ); +#endif +#ifdef ENOTUNIQ + CHECK_HOST_ERRNO( ENOTUNIQ, ENOTUNIQ ); +#endif +#ifdef EBADFD + CHECK_HOST_ERRNO( EBADFD, EBADFD ); +#endif +#ifdef EREMCHG + CHECK_HOST_ERRNO( EREMCHG, EREMCHG ); +#endif +#ifdef ELIBACC + CHECK_HOST_ERRNO( ELIBACC, ELIBACC ); +#endif +#ifdef ELIBBAD + CHECK_HOST_ERRNO( ELIBBAD, ELIBBAD ); +#endif +#ifdef ELIBSCN + CHECK_HOST_ERRNO( ELIBSCN, ELIBSCN ); +#endif +#ifdef ELIBMAX + CHECK_HOST_ERRNO( ELIBMAX, ELIBMAX ); +#endif +#ifdef ELIBEXEC + CHECK_HOST_ERRNO( ELIBEXEC, ELIBEXEC ); +#endif +#ifdef EILSEQ + CHECK_HOST_ERRNO( EILSEQ, EILSEQ ); +#endif +#ifdef ERESTART + CHECK_HOST_ERRNO( ERESTART, ERESTART ); +#endif +#ifdef ESTRPIPE + CHECK_HOST_ERRNO( ESTRPIPE, ESTRPIPE ); +#endif +#ifdef EUSERS + CHECK_HOST_ERRNO( EUSERS, EUSERS ); +#endif +#ifdef ENOTSOCK + CHECK_HOST_ERRNO( ENOTSOCK, ENOTSOCK ); +#endif +#ifdef EDESTADDRREQ + CHECK_HOST_ERRNO( EDESTADDRREQ, EDESTADDRREQ ); +#endif +#ifdef EMSGSIZE + CHECK_HOST_ERRNO( EMSGSIZE, EMSGSIZE ); +#endif +#ifdef EPROTOTYPE + CHECK_HOST_ERRNO( EPROTOTYPE, EPROTOTYPE ); +#endif +#ifdef ENOPROTOOPT + CHECK_HOST_ERRNO( ENOPROTOOPT, ENOPROTOOPT ); +#endif +#ifdef EPROTONOSUPPORT + CHECK_HOST_ERRNO( EPROTONOSUPPORT, EPROTONOSUPPORT ); +#endif +#ifdef ESOCKTNOSUPPORT + CHECK_HOST_ERRNO( ESOCKTNOSUPPORT, ESOCKTNOSUPPORT ); +#endif +#ifdef EOPNOTSUPP + CHECK_HOST_ERRNO( EOPNOTSUPP, EOPNOTSUPP ); +#endif +#ifdef EPFNOSUPPORT + CHECK_HOST_ERRNO( EPFNOSUPPORT, EPFNOSUPPORT ); +#endif +#ifdef EAFNOSUPPORT + CHECK_HOST_ERRNO( EAFNOSUPPORT, EAFNOSUPPORT ); +#endif +#ifdef EADDRINUSE + CHECK_HOST_ERRNO( EADDRINUSE, EADDRINUSE ); +#endif +#ifdef EADDRNOTAVAIL + CHECK_HOST_ERRNO( EADDRNOTAVAIL, EADDRNOTAVAIL ); +#endif +#ifdef ENETDOWN + CHECK_HOST_ERRNO( ENETDOWN, ENETDOWN ); +#endif +#ifdef ENETUNREACH + CHECK_HOST_ERRNO( ENETUNREACH, ENETUNREACH ); +#endif +#ifdef ENETRESET + CHECK_HOST_ERRNO( ENETRESET, ENETRESET ); +#endif +#ifdef ECONNABORTED + CHECK_HOST_ERRNO( ECONNABORTED, ECONNABORTED ); +#endif +#ifdef ECONNRESET + CHECK_HOST_ERRNO( ECONNRESET, ECONNRESET ); +#endif +#ifdef ENOBUFS + CHECK_HOST_ERRNO( ENOBUFS, ENOBUFS ); +#endif +#ifdef EISCONN + CHECK_HOST_ERRNO( EISCONN, EISCONN ); +#endif +#ifdef ENOTCONN + CHECK_HOST_ERRNO( ENOTCONN, ENOTCONN ); +#endif +#ifdef ESHUTDOWN + CHECK_HOST_ERRNO( ESHUTDOWN, ESHUTDOWN ); +#endif +#ifdef ETOOMANYREFS + CHECK_HOST_ERRNO( ETOOMANYREFS, ETOOMANYREFS ); +#endif +#ifdef ETIMEDOUT + CHECK_HOST_ERRNO( ETIMEDOUT, ETIMEDOUT ); +#endif +#ifdef ECONNREFUSED + CHECK_HOST_ERRNO( ECONNREFUSED, ECONNREFUSED ); +#endif +#ifdef EHOSTDOWN + CHECK_HOST_ERRNO( EHOSTDOWN, EHOSTDOWN ); +#endif +#ifdef EHOSTUNREACH + CHECK_HOST_ERRNO( EHOSTUNREACH, EHOSTUNREACH ); +#endif +#ifdef EALREADY + CHECK_HOST_ERRNO( EALREADY, EALREADY ); +#endif +#ifdef EINPROGRESS + CHECK_HOST_ERRNO( EINPROGRESS, EINPROGRESS ); +#endif +#ifdef ESTALE + CHECK_HOST_ERRNO( ESTALE, ESTALE ); +#endif +#ifdef EUCLEAN + CHECK_HOST_ERRNO( EUCLEAN, EUCLEAN ); +#endif +#ifdef ENOTNAM + CHECK_HOST_ERRNO( ENOTNAM, ENOTNAM ); +#endif +#ifdef ENAVAIL + CHECK_HOST_ERRNO( ENAVAIL, ENAVAIL ); +#endif +#ifdef EISNAM + CHECK_HOST_ERRNO( EISNAM, EISNAM ); +#endif +#ifdef EREMOTEIO + CHECK_HOST_ERRNO( EREMOTEIO, EREMOTEIO ); +#endif +#ifdef EDQUOT + CHECK_HOST_ERRNO( EDQUOT, EDQUOT ); +#endif +#ifdef ENOMEDIUM + CHECK_HOST_ERRNO( ENOMEDIUM, ENOMEDIUM ); +#endif +#ifdef EMEDIUMTYPE + CHECK_HOST_ERRNO( EMEDIUMTYPE, EMEDIUMTYPE ); +#endif +#ifdef ECANCELED + CHECK_HOST_ERRNO( ECANCELED, ECANCELED ); +#endif +#ifdef ENOKEY + CHECK_HOST_ERRNO( ENOKEY, ENOKEY ); +#endif +#ifdef EKEYEXPIRED + CHECK_HOST_ERRNO( EKEYEXPIRED, EKEYEXPIRED ); +#endif +#ifdef EKEYREVOKED + CHECK_HOST_ERRNO( EKEYREVOKED, EKEYREVOKED ); +#endif +#ifdef EKEYREJECTED + CHECK_HOST_ERRNO( EKEYREJECTED, EKEYREJECTED ); +#endif +#ifdef EOWNERDEAD + CHECK_HOST_ERRNO( EOWNERDEAD, EOWNERDEAD ); +#endif +#ifdef ENOTRECOVERABLE + CHECK_HOST_ERRNO( ENOTRECOVERABLE, ENOTRECOVERABLE ); +#endif +#ifdef ERFKILL + CHECK_HOST_ERRNO( ERFKILL, ERFKILL ); +#endif +#ifdef EHWPOISON + CHECK_HOST_ERRNO( EHWPOISON, EHWPOISON ); +#endif +#ifdef EFTYPE + CHECK_HOST_ERRNO( EFTYPE, EFTYPE ); +#endif +#ifdef EWOULDBLOCK + CHECK_HOST_ERRNO( EWOULDBLOCK, EWOULDBLOCK ); +#endif +#ifdef EDEADLOCK + CHECK_HOST_ERRNO( EDEADLOCK, EDEADLOCK ); +#endif +#ifdef ENOTSUP + CHECK_HOST_ERRNO( ENOTSUP, ENOTSUP ); +#endif + + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-frame-runner.sh b/tests/tst-frame-runner.sh new file mode 100755 index 0000000..932f15e --- /dev/null +++ b/tests/tst-frame-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-frame-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/frame-pointer.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00008 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-frame.c b/tests/tst-frame.c new file mode 100644 index 0000000..b94b51e --- /dev/null +++ b/tests/tst-frame.c @@ -0,0 +1,178 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" LDI r64@0x00000, 5\n" +" ADJSP -16\n" +" STSP 0, r64@0x00000\n" +" CALL demo\n" +" ADJSP 16\n" +" MOV r64@0x00008, r64@0x7fff8\n" +" HALT\n" +"demo:\n" +" PUSHFP\n" +" SETFP\n" +" ADJSP -16\n" +" LDFP r64@0x00100, 16\n" +" STFP -8, r64@0x00100\n" +" LEAFP r64@0x00108, -8\n" +" LEASP r64@0x00110, 0\n" +" LDI r64@0x00118, 24\n" +" PSUB r64@0x00120, r64@0x00110, r64@0x00118\n" +" SETSP r64@0x00120\n" +" LDI r64@0x00128, 37\n" +" LEASP r64@0x00130, 0\n" +" STOREP r64@0x00130, r64@0x00128\n" +" LDFP r64@0x00138, -8\n" +" LOADP r64@0x00140, r64@0x00130\n" +" IADD r64@0x7fff8, r64@0x00138, r64@0x00140\n" +" SETSP r64@0x00110\n" +" RESTSP\n" +" POPFP\n" +" RET\n"; + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static __mpu_int64_t +reg_i64( unsigned address ) +{ + __mpu_int64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t ptr, saved_sp, saved_fp, badfp; + __mpu_uint32_t psw; + char err[256]; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + CHECK( __cpu_sp == 256, "empty stack SP" ); + CHECK( __cpu_fp == 256, "empty stack FP" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + __cpu_pc = 0; + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "frame/dynamic stack program run" ); + CHECK( reg_i64( 0x00008 ) == 42, "frame example result" ); + CHECK( reg_i64( 0x7fff8 ) == 42, "canonical return value" ); + CHECK( __cpu_sp == 256, "caller cleanup restored SP" ); + CHECK( __cpu_fp == 256, "callee restored caller FP" ); + cpu_asm_image_free( &im ); + + /* LEAFP is based on FP, not on the moving SP. */ + __cpu_sp = 160; + __cpu_fp = 200; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LEAFP; + regop( &i.operand[0], 0x00200 ); + i.operand[1].type = CPU_OPERAND_OFF64; + i.operand[1].value.off64 = -24; + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "LEAFP direct execution" ); + CHECK( __cpu_get_psw() == psw, "LEAFP preserves PSW" ); + ptr = reg_u64( 0x00200 ); + CHECK( __cpu_ptr_segment( ptr ) == CPU_PTR_SEG_STACK, "LEAFP STACK segment" ); + CHECK( __cpu_ptr_offset( ptr ) == 176, "LEAFP uses FP base" ); + CHECK( __cpu_sp == 160, "LEAFP leaves SP unchanged" ); + CHECK( __cpu_fp == 200, "LEAFP leaves FP unchanged" ); + + /* SETSP accepts only a STACK PTR64 and is atomic on faults. */ + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, 144 ) == 0, "make SETSP STACK ptr" ); + set_reg_u64( 0x00208, ptr ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETSP; + regop( &i.operand[0], 0x00208 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "SETSP valid STACK ptr" ); + CHECK( __cpu_sp == 144, "SETSP installs offset" ); + CHECK( __cpu_fp == 200, "SETSP preserves FP" ); + + saved_sp = __cpu_sp; + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, 10 ) == 0, "make DATA ptr" ); + set_reg_u64( 0x00208, ptr ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "SETSP rejects DATA ptr" ); + CHECK( __cpu_sp == saved_sp, "SETSP pointer fault atomic" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, 300 ) == 0, "make out-of-range STACK ptr" ); + set_reg_u64( 0x00208, ptr ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "SETSP checks allocated stack size" ); + CHECK( __cpu_sp == saved_sp, "SETSP stack fault atomic" ); + + /* PUSHFP/POPFP preserve exact previous FP and validate a restored FP. */ + __cpu_sp = 128; + __cpu_fp = 216; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PUSHFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PUSHFP" ); + CHECK( __cpu_sp == 120, "PUSHFP moves SP by eight bytes" ); + memcpy( &saved_fp, __cpu_stack_ptr() + 120, 8 ); + CHECK( saved_fp == 216, "PUSHFP stored FP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "SETFP" ); + CHECK( __cpu_fp == 120, "SETFP copies SP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RESTSP; + __cpu_sp = 80; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "RESTSP" ); + CHECK( __cpu_sp == 120, "RESTSP copies FP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_POPFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "POPFP" ); + CHECK( __cpu_fp == 216, "POPFP restores caller FP" ); + CHECK( __cpu_sp == 128, "POPFP advances SP by eight bytes" ); + CHECK( __cpu_get_psw() == psw, "FP/SP management preserves PSW" ); + + /* Invalid saved FP must not partially change FP or SP. */ + __cpu_sp = 64; + __cpu_fp = 100; + badfp = 257; + memcpy( __cpu_stack_ptr() + 64, &badfp, 8 ); + saved_sp = __cpu_sp; + saved_fp = __cpu_fp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "POPFP rejects invalid saved FP" ); + CHECK( __cpu_sp == saved_sp, "POPFP fault preserves SP" ); + CHECK( __cpu_fp == saved_fp, "POPFP fault preserves FP" ); + + __cpu_free_context(); + CHECK( __cpu_fp == 0, "FP cleared by free context" ); + puts( "libcpu frame-pointer/dynamic STACK regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-hll-crt-runner.sh b/tests/tst-hll-crt-runner.sh new file mode 100755 index 0000000..ca03641 --- /dev/null +++ b/tests/tst-hll-crt-runner.sh @@ -0,0 +1,338 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${MCPU_TEST_ROOT:?}" "${MCPU_HLL_FIXTURE_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-hll-crt-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +as() +{ + "$CPU_AS" -o "$2" "$1" >/dev/null +} + +must_fail_link() +{ + if "$CPU_LD" "$@" >/dev/null 2>&1; then + echo "mcpu-ld unexpectedly accepted invalid HLL input: $*" >&2 + exit 1 + fi +} + +# --------------------------------------------------------------------------- +# Regression fixtures for compiler-normalized HLL objects and libc hooks. +# --------------------------------------------------------------------------- +as "$MCPU_HLL_FIXTURE_SRCDIR/program-main.S" "$TMP/program.o" +as "$MCPU_HLL_FIXTURE_SRCDIR/model-pendulum-life.S" "$TMP/model.o" +as "$MCPU_HLL_FIXTURE_SRCDIR/libc-stub.S" "$TMP/libc-stub.o" + +# The future compiler output owns HLL functions, never MELF lifecycle symbols. +"$CPU_READELF" -s "$TMP/program.o" > "$TMP/program.syms" +grep -q ' main$' "$TMP/program.syms" +grep -q ' fini$' "$TMP/program.syms" +if grep -Eq ' (_start|_life|_fini)$' "$TMP/program.syms"; then + echo "PROGRAM compiler-output emulator defines lifecycle symbols" >&2 + exit 1 +fi + +"$CPU_READELF" -s "$TMP/model.o" > "$TMP/model.syms" +grep -q ' init$' "$TMP/model.syms" +grep -q ' life$' "$TMP/model.syms" +grep -q ' fini$' "$TMP/model.syms" +if grep -Eq ' (_start|_life|_fini)$' "$TMP/model.syms"; then + echo "MODEL compiler-output emulator defines lifecycle symbols" >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# PROGRAM: automatic classification, CRT insertion, argc/argv bridge and fini. +# --------------------------------------------------------------------------- +"$CPU_LD" --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/program" "$TMP/program.o" "$TMP/libc-stub.o" >/dev/null + +"$CPU_READELF" -h "$TMP/program" > "$TMP/program.hdr" +entry=`sed -n 's/^ Entry point address:[[:space:]]*//p' "$TMP/program.hdr"` +init=`sed -n 's/^ INIT entry address:[[:space:]]*//p' "$TMP/program.hdr"` +life=`sed -n 's/^ LIFE entry address:[[:space:]]*//p' "$TMP/program.hdr"` +fini=`sed -n 's/^ FINI entry address:[[:space:]]*//p' "$TMP/program.hdr"` +test "x$entry" = "x$life" +test "x$init" = "x0x0000000000000000" +test "x$fini" != "x0x0000000000000000" + +set +e +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# ABI-v3 PROGRAM uses the same HLL interface with the runtime hooks supplied +# by a normal shared MCPU Libc dependency. The four hook names must therefore +# be exported from ET_DYN, while explicit preloads still may not interpose them. +"$CPU_LD" --shared --soname=libhllc.so.1 \ + -o "$TMP/libhllc.so.1" "$TMP/libc-stub.o" >/dev/null +ln -s libhllc.so.1 "$TMP/libhllc.so" +"$CPU_READELF" -s "$TMP/libhllc.so.1" > "$TMP/libhllc.syms" +grep -q ' __mcpu_libc_init$' "$TMP/libhllc.syms" +grep -q ' __mcpu_init_array$' "$TMP/libhllc.syms" +grep -q ' __mcpu_fini_array$' "$TMP/libhllc.syms" +grep -q ' __mcpu_libc_fini$' "$TMP/libhllc.syms" + +"$CPU_LD" --dynamic --sys-root="$MCPU_TEST_ROOT" \ + --stack-size=4K --no-heap -L"$TMP" -lhllc \ + -o "$TMP/program-dyn" "$TMP/program.o" >/dev/null +set +e +out=`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP" \ + --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program-dyn" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# HLL complete-program functions remain bound to the main executable. Runtime +# hooks come from the normal libc dependency. A preload may export all of the +# same names but cannot interpose either class of protected HLL symbol. +cat > "$TMP/override.s" <<'EOS' +.global main +.global fini +.global __mcpu_libc_init +.global __mcpu_init_array +.global __mcpu_fini_array +.global __mcpu_libc_fini +.text +main: + LDI r64@0x70000, 700 + LDI r32@0x7fffc, 70 + RET +fini: + LDI r64@0x70000, 701 + RET +__mcpu_libc_init: + LDI r64@0x70000, 702 + RET +__mcpu_init_array: + LDI r64@0x70000, 703 + RET +__mcpu_fini_array: + LDI r64@0x70000, 704 + RET +__mcpu_libc_fini: + LDI r64@0x70000, 705 + RET +EOS +as "$TMP/override.s" "$TMP/override.o" +"$CPU_LD" --shared --soname=libhll-override.so.1 \ + -o "$TMP/libhll-override.so" "$TMP/override.o" >/dev/null +set +e +out=`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP" \ + --ldpreload="$TMP/libhll-override.so" \ + --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program-dyn" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# --------------------------------------------------------------------------- +# MODEL: compact pendulum-shaped compiler-output regression fixture. +# --------------------------------------------------------------------------- +"$CPU_LD" --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/model" "$TMP/model.o" "$TMP/libc-stub.o" >/dev/null + +"$CPU_READELF" -h "$TMP/model" > "$TMP/model.hdr" +entry=`sed -n 's/^ Entry point address:[[:space:]]*//p' "$TMP/model.hdr"` +init=`sed -n 's/^ INIT entry address:[[:space:]]*//p' "$TMP/model.hdr"` +life=`sed -n 's/^ LIFE entry address:[[:space:]]*//p' "$TMP/model.hdr"` +fini=`sed -n 's/^ FINI entry address:[[:space:]]*//p' "$TMP/model.hdr"` +test "x$entry" = "x$init" +test "x$life" != "x$entry" +test "x$fini" != "x0x0000000000000000" + +# Natural MODEL run: three LIFE_CONTINUE boundaries, then LIFE_END. +set +e +out=`"$CPU_RUN" --steps=10 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 0 +expected='1 +2 +3 +6 1' +test "x$out" = "x$expected" + +# init() failure is already RUNNING: normal HLL finalization is mandatory. +set +e +out=`"$CPU_RUN" --before-run=set-int-control:64@0=-1 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 17 +test "x$out" = "x6 1" + +# Positive life() status is a MODEL error and performs normal finalization. +set +e +out=`"$CPU_RUN" --steps=10 \ + --before-run=set-int-control:64@8=2 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 9 +expected='1 +6 1' +test "x$out" = "x$expected" + +# Diagnostic --steps stop does not synthesize FINI. +out=`"$CPU_RUN" --steps=1 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +expected='1 +3 0' +test "x$out" = "x$expected" + +# --------------------------------------------------------------------------- +# PRE_RUNTIME failure: exercise the real common CRT startup/finalization code. +# A test-only PROGRAM adapter corrupts McpuStartup.version before entering the +# real __mcpu_crt_startup. The normal user object and libc stub are unchanged. +# --------------------------------------------------------------------------- +mkdir -p "$TMP/bad-root/lib/mcpu" +cp "$MCPU_TEST_ROOT/lib/mcpu/crti.o" "$TMP/bad-root/lib/mcpu/" +cp "$MCPU_TEST_ROOT/lib/mcpu/crtn.o" "$TMP/bad-root/lib/mcpu/" +cp "$MCPU_TEST_ROOT/lib/mcpu/crt1.o" "$TMP/bad-root/lib/mcpu/" +cat > "$TMP/bad-crt0.s" <<'EOS' +.global __mcpu_crt_startup +.global main +.text +__mcpu_crt_hll_program: +_start_life: + LDI r32@0x7fe00, 99 + STSP 0, r32@0x7fe00 + CALL __mcpu_crt_startup + LDI r32@0x7fe08, 88 + EXIT r32@0x7fe08 +EOS +as "$TMP/bad-crt0.s" "$TMP/bad-root/lib/mcpu/crt0.o" +"$CPU_LD" --sys-root="$TMP/bad-root" --stack-size=4K --no-heap \ + -o "$TMP/bad-startup" "$TMP/program.o" "$TMP/libc-stub.o" >/dev/null +set +e +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/bad-startup" alpha beta` +status=$? +set -e +test "$status" -ne 0 +test "x$out" = "x0" + +# --------------------------------------------------------------------------- +# HLL complete-program classification errors. +# --------------------------------------------------------------------------- +cat > "$TMP/main-only.s" <<'EOS' +.global main +.text +main: + CLR r32@0x7fffc + RET +EOS +as "$TMP/main-only.s" "$TMP/main-only.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-main-only" "$TMP/main-only.o" "$TMP/libc-stub.o" + +cat > "$TMP/model-init.s" <<'EOS' +.global init +.global fini +.text +init: + CLR r32@0x7fffc + RET +fini: + RET +EOS +cat > "$TMP/model-life.s" <<'EOS' +.global life +.text +life: + CLR r32@0x7fffc + RET +EOS +as "$TMP/model-init.s" "$TMP/model-init.o" +as "$TMP/model-life.s" "$TMP/model-life.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-split-model" "$TMP/model-init.o" "$TMP/model-life.o" \ + "$TMP/libc-stub.o" + +cat > "$TMP/mixed.s" <<'EOS' +.global main +.global fini +.text +_start_life: +main: + CLR r32@0x7fffc + RET +fini: + RET +EOS +as "$TMP/mixed.s" "$TMP/mixed.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-mixed" "$TMP/mixed.o" "$TMP/libc-stub.o" + +cat > "$TMP/reserved.s" <<'EOS' +.global __mcpu_crt_user_collision +.text +__mcpu_crt_user_collision: + RET +EOS +as "$TMP/reserved.s" "$TMP/reserved.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-reserved" "$TMP/reserved.o" + +# --------------------------------------------------------------------------- +# Frozen ET_DYN ownership rule: model/lifecycle sections belong to ET_EXEC. +# --------------------------------------------------------------------------- +for sec in init fini control input output trace; do + case "$sec" in + init|fini) + cat > "$TMP/shared-$sec.s" <<EOS +.$sec +shared_${sec}_object: + RET +EOS + ;; + *) + cat > "$TMP/shared-$sec.s" <<EOS +.$sec +shared_${sec}_object: + .integer 64, 0 +EOS + ;; + esac + as "$TMP/shared-$sec.s" "$TMP/shared-$sec.o" + if "$CPU_LD" --shared --soname="libbad-$sec.so.1" \ + -o "$TMP/libbad-$sec.so" "$TMP/shared-$sec.o" >/dev/null 2>&1; then + echo "ET_DYN unexpectedly accepted .$sec" >&2 + exit 1 + fi +done + +cat > "$TMP/shared-lifecycle.s" <<'EOS' +.text +_start_life: + END +EOS +as "$TMP/shared-lifecycle.s" "$TMP/shared-lifecycle.o" +if "$CPU_LD" --shared --soname=libbad-lifecycle.so.1 \ + -o "$TMP/libbad-lifecycle.so" "$TMP/shared-lifecycle.o" >/dev/null 2>&1; then + echo "ET_DYN unexpectedly accepted lifecycle symbols" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-immediate-exactness-runner.sh b/tests/tst-immediate-exactness-runner.sh new file mode 100755 index 0000000..3fc3a42 --- /dev/null +++ b/tests/tst-immediate-exactness-runner.sh @@ -0,0 +1,65 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" +TMP="${TMPDIR:-/tmp}/libcpu-imm-$$" +trap 'rm -f "$TMP".s "$TMP".o "$TMP".err "$TMP".out' EXIT HUP INT TERM +cat > "$TMP.s" <<'EOS' +.text +LDR r4096@0x00000, 3.14243987129849274029174912694696912849214e-286 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact wide LDR" >&2 + exit 1 +fi +grep -F "$TMP.s:2: error:" "$TMP.err" >/dev/null +grep -F 'LDR immediate is not exactly representable as real64;' "$TMP.err" >/dev/null +grep -F 'use .real <bits>, value in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDR r32@0x00000, 0.1 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact real32 LDR" >&2 + exit 1 +fi +grep -F 'LDR immediate is not exactly representable as real32;' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDC c128@0x00000, 0.1r0+32.0j0 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact complex immediate" >&2 + exit 1 +fi +grep -F 'LDC immediate component is not exactly representable as real32;' "$TMP.err" >/dev/null +grep -F 'use .complex <bits>, real, imag in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDI r8@0x00000, 255 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted lossy narrow integer immediate" >&2 + exit 1 +fi +grep -F 'LDI immediate is not exactly representable as integer8;' "$TMP.err" >/dev/null +grep -F 'use .integer <bits>, value in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDI r128@0x00000, 9223372036854775808 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted integer wider than IMM64" >&2 + exit 1 +fi +grep -F 'LDI immediate is not exactly representable as integer64;' "$TMP.err" >/dev/null + +echo 'libcpu immediate exactness runner regression: PASS' diff --git a/tests/tst-immediate-exactness.c b/tests/tst-immediate-exactness.c new file mode 100644 index 0000000..ed054e7 --- /dev/null +++ b/tests/tst-immediate-exactness.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",m); return 1; } } while(0) + +static int +rejected( const char *src, const char *needle ) +{ + cpu_asm_image im; + char err[512]; + int rc = cpu_assemble_text( src, &im, err, sizeof( err ) ); + if( rc == 0 ) + { + cpu_asm_image_free( &im ); + return( 0 ); + } + return( strstr( err, needle ) != 0 ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[512]; + static const char *prec = "3.14243987129849274029174912694696912849214e-286"; + char src[4096]; + + __cpu_init(); + + CHECK( cpu_assemble_text( ".text\nLDR r32@0x00000, -17.0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact real32 LDR accepted"); + cpu_asm_image_free( &im ); + + CHECK( cpu_assemble_text( ".text\nLDR r4096@0x00000, 2.0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact real64 immediate widened to real4096 accepted"); + cpu_asm_image_free( &im ); + + CHECK( rejected( ".text\nLDR r32@0x00000, 0.1\nHALT\n", + "LDR immediate is not exactly representable as real32;"), + "inexact real32 immediate rejected"); + + CHECK( rejected( ".text\nLDR r64@0x00000, 0.1\nHALT\n", + "LDR immediate is not exactly representable as real64;"), + "inexact real64 immediate rejected"); + + snprintf( src, sizeof( src ), ".text\nLDR r64@0x00000, %s\nHALT\n", prec ); + CHECK( rejected( src, "LDR immediate is not exactly representable as real64;" ), + "inexact real64 immediate rejected"); + + snprintf( src, sizeof( src ), ".text\nLDR r4096@0x00000, %s\nHALT\n", prec ); + CHECK( rejected( src, "LDR immediate is not exactly representable as real64;" ), + "wide LDR cannot hide binary64 precision loss"); + + CHECK( cpu_assemble_text( ".text\nLDC c64@0x00000, -17.0r0+32.0j0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact complex64 immediate accepted"); + cpu_asm_image_free( &im ); + + CHECK( rejected( ".text\nLDC c128@0x00000, 0.1r0+32.0j0\nHALT\n", + "LDC immediate component is not exactly representable as real32;"), + "inexact complex immediate component rejected"); + + CHECK( rejected( ".text\nLDC c32@0x00000, 1.0r0+2.0j0\nHALT\n", + "bad LDC destination"), + "complex destination below 64 bits rejected"); + + CHECK( rejected( ".text\nLDI r128@0x00000, 9223372036854775808\nHALT\n", + "LDI immediate is not exactly representable as integer64;"), + "integer wider than signed IMM64 rejected"); + + CHECK( rejected( ".text\nLDI r8@0x00000, 255\nHALT\n", + "LDI immediate is not exactly representable as integer8;"), + "narrow integer immediate that changes value rejected"); + CHECK( cpu_assemble_text( ".text\nLDI r8@0x00000, -1\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact signed integer8 immediate accepted"); + cpu_asm_image_free( &im ); + + snprintf( src, sizeof( src ), + ".data\n" + ".align 16\n" + "wide_real:\n" + " .real 4096, %s\n" + "wide_int:\n" + " .integer 128, 9223372036854775808\n" + "wide_complex:\n" + " .complex 4096, %s, -%s\n" + ".text\n" + " LOAD r4096@0x00000, wide_real\n" + " LOAD r128@0x00200, wide_int\n" + " LOAD c4096@0x00400, wide_complex\n" + " HALT\n", prec, prec, prec); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, + "DATA fallback with .real/.integer/.complex and LOAD accepted"); + cpu_asm_image_free( &im ); + + __cpu_free_context(); + puts( "libcpu immediate exactness regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-indirect-control.c b/tests/tst-indirect-control.c new file mode 100644 index 0000000..acb0a7b --- /dev/null +++ b/tests/tst-indirect-control.c @@ -0,0 +1,184 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char decode_src[]= +".text\n" +" JMPP r64@0x00000\n" +" CALLP r64@0x00008\n" +" HALT\n"; + +static const char run_src[]= +".data\n" +".align 8\n" +"table:\n" +" .cptr leaf\n" +" .cptr done\n" +".text\n" +" LEA r64@0x00000, table\n" +" LOADP r64@0x00008, r64@0x00000\n" +" CALLP r64@0x00008\n" +" PADDI r64@0x00000, r64@0x00000, 8\n" +" LOADP r64@0x00010, r64@0x00000\n" +" JMPP r64@0x00010\n" +" LDI r64@0x00020, 99\n" +" HALT\n" +"leaf:\n" +" LDI r64@0x00020, 42\n" +" RET\n" +"done:\n" +" HALT\n"; + +static void +put_u64_reg( unsigned addr, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, addr ), v ); +} + +int +main( void ) +{ + cpu_asm_image decim, im, bad; + cpu_instruction insn, execi; + cpu_register r64 = + { + 64, 0x00100 + } + , r32 = + { + 32, 0x00100 + }; + __mpu_uint64_t p0, p1, pvalid, pbad, ret, saved_sp, saved_pc; + __mpu_uint32_t psw; + mpu_int64_t forty_two; + unsigned char before[16], after[16]; + char err[256]; + int rc; + + __cpu_init(); + + CHECK( cpu_assemble_text( decode_src, &decim, err, sizeof( err ) ) == 0, err ); + CHECK( decim.code_size == 12, "JMPP/CALLP bytecode lengths" ); + CHECK( __cpu_program_set( decim.code, decim.code_size ) == 0, "decode program set" ); + __cpu_pc = 0; + CHECK( __cpu_decode( &insn ) == CPU_EXEC_SUCCESS, "decode JMPP" ); + CHECK( insn.opcode == CPU_OP_JMPP, "JMPP opcode" ); + CHECK( insn.length == CPU_OPCODE_SIZE + CPU_REG24_SIZE, "JMPP length 5" ); + CHECK( insn.operand[0].type == CPU_OPERAND_REGISTER, "JMPP register operand" ); + CHECK( insn.operand[0].value.reg.bits == 64, "JMPP r64 width" ); + __cpu_pc = 5; + CHECK( __cpu_decode( &insn ) == CPU_EXEC_SUCCESS, "decode CALLP" ); + CHECK( insn.opcode == CPU_OP_CALLP, "CALLP opcode" ); + CHECK( insn.length == CPU_OPCODE_SIZE + CPU_REG24_SIZE, "CALLP length 5" ); + cpu_asm_image_free( &decim ); + + __cpu_program_free(); + CHECK( cpu_assemble_text( run_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.data_size == CPU_DATA_NULL_GUARD_SIZE + 16, "two .cptr entries in DATA" ); + p0 = __cpu_data_get_u64( im.data + CPU_DATA_NULL_GUARD_SIZE ); + p1 = __cpu_data_get_u64( im.data + CPU_DATA_NULL_GUARD_SIZE + 8 ); + CHECK( __cpu_ptr_segment( p0 ) == CPU_PTR_SEG_PROGRAM, "first .cptr PROGRAM segment" ); + CHECK( __cpu_ptr_segment( p1 ) == CPU_PTR_SEG_PROGRAM, "second .cptr PROGRAM segment" ); + CHECK( __cpu_ptr_offset( p0 ) < im.code_size, "first .cptr target in PROGRAM" ); + CHECK( __cpu_ptr_offset( p1 ) < im.code_size, "second .cptr target in PROGRAM" ); + CHECK( p0 != p1, ".cptr labels resolve to distinct targets" ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "indirect program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "indirect data set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "indirect stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "indirect CALLP/JMPP run" ); + iatoi( ( mpu_int * ) forty_two, "42", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00020 ), forty_two, NB_I64 ), "CALLP returned and JMPP reached done" ); + CHECK( __cpu_sp == 512, "CALLP/RET restored SP" ); + + /* Direct executor tests: JMPP must only install a validated PROGRAM PTR64. */ + memset( &execi, 0, sizeof( execi ) ); + execi.opcode = CPU_OP_JMPP; + execi.operand[0].type = CPU_OPERAND_REGISTER; + execi.operand[0].value.reg = r64; + pvalid = p0; + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 7; + __cpu_sp = 512; + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_BRANCH, "JMPP executor success" ); + CHECK( __cpu_pc == __cpu_ptr_offset( pvalid ), "JMPP installed target PC" ); + CHECK( __cpu_sp == 512, "JMPP does not change SP" ); + CHECK( __cpu_get_psw() == psw, "JMPP preserves PSW" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 9; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects non-PROGRAM PTR64" ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp, "JMPP pointer fault is atomic" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_PROGRAM, im.code_size ) == 0, "make out-of-range PROGRAM pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 11; + saved_pc = __cpu_pc; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects PROGRAM end pointer" ); + CHECK( __cpu_pc == saved_pc, "JMPP range fault preserves PC" ); + + put_u64_reg( 0x00100, CPU_NULL_PTR ); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects NULL PTR64" ); + + execi.operand[0].value.reg = r32; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_WIDTH_FAULT, "JMPP requires physical r64" ); + + /* CALLP validates pointer, return PC and stack before architectural mutation. */ + memset( &execi, 0, sizeof( execi ) ); + execi.opcode = CPU_OP_CALLP; + execi.operand[0].type = CPU_OPERAND_REGISTER; + execi.operand[0].value.reg = r64; + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 10; + __cpu_sp = 512; + memset( __cpu_stack_ptr() + 496, 0xa5, 16 ); + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_BRANCH, "CALLP executor success" ); + CHECK( __cpu_pc == __cpu_ptr_offset( pvalid ), "CALLP installed target PC" ); + CHECK( __cpu_sp == 504, "CALLP pushed 8-byte return PC" ); + ret = __cpu_data_get_u64( __cpu_stack_ptr() + 504 ); + CHECK( ret == 15, "CALLP return PC is next 5-byte instruction" ); + CHECK( __cpu_get_psw() == psw, "CALLP preserves PSW" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_STACK, 0 ) == 0, "make STACK pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 20; + __cpu_sp = 512; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + memcpy( before, __cpu_stack_ptr() + 496, 16 ); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "CALLP rejects non-PROGRAM PTR64" ); + memcpy( after, __cpu_stack_ptr() + 496, 16 ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp && !memcmp( before, after, 16 ), "CALLP pointer fault is atomic" ); + + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 20; + __cpu_sp = 4; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_STACK_FAULT, "CALLP checks return-address stack space" ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp, "CALLP stack fault is atomic" ); + + execi.operand[0].value.reg = r32; + __cpu_sp = 512; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_WIDTH_FAULT, "CALLP requires physical r64" ); + + CHECK( cpu_assemble_text( ".text\n JMPP r32@0x00000\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects JMPP r32" ); + CHECK( cpu_assemble_text( ".text\n CALLP c64@0x00000\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects CALLP c64 spelling" ); + CHECK( cpu_assemble_text( ".data\n .cptr nowhere\n.text\n HALT\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects undefined .cptr label" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu indirect control-flow regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-indirect-runner.sh b/tests/tst-indirect-runner.sh new file mode 100755 index 0000000..dba3b94 --- /dev/null +++ b/tests/tst-indirect-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-indirect-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/indirect-control.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00020 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-instruction.c b/tests/tst-instruction.c new file mode 100644 index 0000000..052fb9c --- /dev/null +++ b/tests/tst-instruction.c @@ -0,0 +1,237 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_REGISTER; + operand.value.reg.bits = bits; + operand.value.reg.address = address; + + return( operand ); +} + + +static void +clear_instruction( cpu_instruction *instruction, cpu_opcode opcode ) +{ + memset( instruction, 0, sizeof(*instruction) ); + instruction->opcode = opcode; +} + + +int +main( void ) +{ + cpu_instruction instruction; + __mpu_uint32_t psw; + + __cpu_init(); + + /* NOP must not change the logical PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_TF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + + clear_instruction( &instruction, CPU_OP_NOP ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "NOP return code" ); + CHECK( __cpu_get_psw() == psw, "NOP preserves PSW" ); + + /* MOV is a raw physical-register copy and accepts overlap. */ + { + __mpu_UNIT_t *src = __cpu_reg_ptr( 128, 0x00100 ); + __mpu_UNIT_t *dst = __cpu_reg_ptr( 128, 0x00110 ); + unsigned int i; + + CHECK( src != 0 && dst != 0, "MOV register pointers" ); + + for( i = 0; i < 16; ++i ) src[i] = (__mpu_UNIT_t)( 0x20 + i ); + memset( dst, 0, 16 ); + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00110 ); + instruction.operand[1] = reg_operand( 128, 0x00100 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "MOV return code" ); + CHECK( memcmp( dst, src, 16 ) == 0, "MOV register contents" ); + CHECK( __cpu_get_psw() == psw, "MOV preserves PSW" ); + + /* Overlap must have memmove semantics. */ + for( i = 0; i < 32; ++i ) __cpu_registers[0x00200 + i] = (__mpu_UNIT_t)i; + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00210 ); + instruction.operand[1] = reg_operand( 128, 0x00200 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, + "overlapping MOV return code" ); + for( i = 0; i < 16; ++i ) + CHECK( __cpu_registers[0x00210 + i] == (__mpu_UNIT_t)i, + "overlapping MOV contents" ); + } + + /* CLR clears physical storage and leaves PSW unchanged. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 256, 0x00300 ); + unsigned int i; + + CHECK( dst != 0, "CLR register pointer" ); + memset( dst, 0xa5, 32 ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0] = reg_operand( 256, 0x00300 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLR return code" ); + for( i = 0; i < 32; ++i ) CHECK( dst[i] == 0, "CLR register contents" ); + CHECK( __cpu_get_psw() == psw, "CLR preserves PSW" ); + } + + /* IADD delegates arithmetic and integer FLAGS to LibMPU. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00400 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00401 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00402 ); + + *a = 0xff; + *b = 0x01; + *dst = 0x5a; + + __cpu_stt(); + __cpu_sti(); + CHECK( __cpu_stmode( 2 ) == 0, "set CPU status before IADD" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 8, 0x00400 ); + instruction.operand[1] = reg_operand( 8, 0x00401 ); + instruction.operand[2] = reg_operand( 8, 0x00402 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADD return code" ); + CHECK( *dst == 0x00, "IADD result" ); + CHECK( __mpu_gtc() == 1, "IADD carry flag" ); + CHECK( __mpu_gtz() == 1, "IADD zero flag" ); + CHECK( __cpu_gtt() == 1, "IADD preserves TF" ); + CHECK( __cpu_gti() == 1, "IADD preserves IF" ); + CHECK( __cpu_gtmode() == 2, "IADD preserves MODE" ); + } + + /* IADC consumes LibMPU carry from the preceding operation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00403 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00404 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00405 ); + + *a = 1; + *b = 2; + *dst = 0; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00403 ); + instruction.operand[1] = reg_operand( 8, 0x00404 ); + instruction.operand[2] = reg_operand( 8, 0x00405 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC return code" ); + CHECK( *dst == 4, "IADC result with carry" ); + } + + /* ISUB and ISBB use the same three-register representation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00406 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00407 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00408 ); + + *a = 9; + *b = 3; + + clear_instruction( &instruction, CPU_OP_ISUB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISUB return code" ); + CHECK( *dst == 6, "ISUB result" ); + + *a = 9; + *b = 3; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_ISBB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISBB return code" ); + CHECK( *dst == 5, "ISBB result with borrow" ); + } + + /* Operand validation belongs to the common executor. */ + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 16, 0x00500 ); + instruction.operand[1] = reg_operand( 32, 0x00504 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_WIDTH_FAULT, + "MOV width mismatch" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 32, 0x00501 ); /* unaligned */ + instruction.operand[1] = reg_operand( 32, 0x00504 ); + instruction.operand[2] = reg_operand( 32, 0x00508 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_REGISTER_FAULT, + "invalid register operand" ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0].type = CPU_OPERAND_IMMEDIATE; + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "unsupported operand type" ); + + clear_instruction( &instruction, (cpu_opcode)0x7fff ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPCODE_FAULT, + "unknown opcode" ); + + CHECK( __cpu_exec( (const cpu_instruction *)0 ) == CPU_EXEC_INSTRUCTION_FAULT, + "NULL instruction" ); + + /* HALT sets only the CPU-owned HF part of PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_IF_MASK ); + clear_instruction( &instruction, CPU_OP_HALT ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "HALT return code" ); + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) != 0, "HALT sets HF" ); + CHECK( (psw & PSW_CF_MASK) != 0, "HALT preserves CF" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "HALT preserves INXF" ); + CHECK( (psw & PSW_IF_MASK) != 0, "HALT preserves IF" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu instruction regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 decoded instruction/executor regression: PASS\n" ); + printf( "opcodes: NOP HALT MOV CLR IADD IADC ISUB ISBB\n" ); + printf( "binary encoding and addressing modes are not defined yet\n" ); + + return( 0 ); +} diff --git a/tests/tst-library-search-runner.sh b/tests/tst-library-search-runner.sh new file mode 100755 index 0000000..6c5ee73 --- /dev/null +++ b/tests/tst-library-search-runner.sh @@ -0,0 +1,118 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-library-search-$$ +HOME="$TMP/home" +export HOME +mkdir -p "$HOME/.mcpu/etc" "$TMP/root/lib" "$TMP/first" "$TMP/second" "$TMP/user/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` + +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/a.o" "$EXAMPLES/dynamic-liba.s" +"$CPU_AS" -o "$TMP/b.o" "$EXAMPLES/dynamic-libb.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -o "$TMP/main-program" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/libb.so" "$TMP/b.o" + +cp "$TMP/liba.so" "$TMP/root/lib/liba.so" +cp "$TMP/libb.so" "$TMP/root/lib/libb.so" +cp "$TMP/liba.so" "$TMP/root/lib/libpick.so" +cp "$TMP/libb.so" "$TMP/first/libpick.so" +cp "$TMP/liba.so" "$TMP/first/libfirst.so" +cp "$TMP/liba.so" "$TMP/first/liborder.so" +cp "$TMP/libb.so" "$TMP/second/libpick.so" +cp "$TMP/libb.so" "$TMP/second/libsecond.so" +cp "$TMP/libb.so" "$TMP/second/liborder.so" + +# A bare preload name is resolved through <sys-root>/lib. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=liba.so "$TMP/main-program"`" = 42 ] + +# Explicit --library-path directories have priority over the system tree. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --int-reg=r64@0x00018 --ldpreload=libpick.so "$TMP/main-program"`" = 43 ] + +# Colon-list order is the actual search order. +[ "`"$CPU_RUN" --sys-root="$TMP/root" \ + --library-path="$TMP/first:$TMP/second" --int-reg=r64@0x00018 \ + --ldpreload=liborder.so "$TMP/main-program"`" = 42 ] +[ "`"$CPU_RUN" --sys-root="$TMP/root" \ + --library-path="$TMP/second:$TMP/first" --int-reg=r64@0x00018 \ + --ldpreload=liborder.so "$TMP/main-program"`" = 43 ] + +# Repeated --library-path occurrences append in command-line order. +cat > "$TMP/run-search.expected" <<EOT +libraries: + $TMP/first + $TMP/second + $TMP/root/lib +EOT +"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --library-path "$TMP/second" --print-search-dirs > "$TMP/run-search.txt" +cmp "$TMP/run-search.expected" "$TMP/run-search.txt" + +# mcpu-ld uses the same common search state through -L. +cat > "$TMP/ld-search.expected" <<EOT +libraries: + $TMP/first + $TMP/second + $TMP/root/lib +EOT +"$CPU_LD" --sys-root="$TMP/root" -L"$TMP/first" -L "$TMP/second" \ + --print-search-dirs > "$TMP/ld-search.txt" +cmp "$TMP/ld-search.expected" "$TMP/ld-search.txt" + +# Explicit pathnames keep the historical behavior and bypass name lookup. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --int-reg=r64@0x00018 --ldpreload="$TMP/root/lib/liba.so" \ + "$TMP/main-program"`" = 42 ] + +# Resolving a list does not change the established rightmost preload priority. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=liba.so:libb.so "$TMP/main-program"`" = 43 ] +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=libb.so:liba.so "$TMP/main-program"`" = 42 ] + +# The configured system path participates in real name lookup. +cp "$TMP/liba.so" "$TMP/user/lib/libuser.so" +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/user/lib; +EOC +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload=libuser.so \ + "$TMP/main-program"`" = 42 ] + +# An explicitly empty system path has no fallback, while an explicit path works. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +if "$CPU_RUN" --ldpreload=liba.so "$TMP/main-program" >/dev/null 2>&1; then + echo "bare preload unexpectedly resolved with empty system path" >&2 + exit 1 +fi +[ "`"$CPU_RUN" --library-path="$TMP/root/lib" --int-reg=r64@0x00018 \ + --ldpreload=liba.so "$TMP/main-program"`" = 42 ] + +# Missing names and empty path-list elements are errors. +if "$CPU_RUN" --sys-root="$TMP/root" --ldpreload=does-not-exist.so \ + "$TMP/main-program" >/dev/null 2>&1; then + echo "missing preload name unexpectedly resolved" >&2 + exit 1 +fi +if "$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first::$TMP/second" \ + --print-search-dirs >/dev/null 2>&1; then + echo "empty --library-path element unexpectedly accepted" >&2 + exit 1 +fi + +# 0.2.15 exposes -l for real link-time dependency resolution. Search-dir +# diagnostics remain independent of whether a later link uses library operands. +"$CPU_LD" --help 2>&1 | grep -q -- '-l <name>' + +"$CPU_RUN" --help 2>&1 | grep -q -- '--library-path' +"$CPU_LD" --help 2>&1 | grep -q -- '-L <dir>' + +exit 0 diff --git a/tests/tst-lifecycle-runner.sh b/tests/tst-lifecycle-runner.sh new file mode 100755 index 0000000..c25a29a --- /dev/null +++ b/tests/tst-lifecycle-runner.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-lifecycle-$$" +trap 'rm -f "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/model-lifecycle.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --init-only --int-reg=r64@0x00000 "$base.melf"` +test "x$out" = "x40" +out=`"$CPU_RUN" --steps=1 --int-reg=r64@0x00000 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-lifecycle.c b/tests/tst-lifecycle.c new file mode 100644 index 0000000..6e2a7d6 --- /dev/null +++ b/tests/tst-lifecycle.c @@ -0,0 +1,63 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +static const char src[]= +".init\n" +"_start:\n" +" LDI r64@0x00000, 40\n" +" END\n" +".text\n" +"_life:\n" +" IINC r64@0x00000, r64@0x00000\n" +" END\n" +" IINC r64@0x00000, r64@0x00000\n" +" LDI r32@0x00020, 7\n" +" EXIT r32@0x00020\n" +".fini\n" +"_fini:\n" +" LDI r64@0x00008, 99\n" +" END\n"; +static __mpu_uint64_t +get64( unsigned a ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, a ) ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + int rc; + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.has_init && im.has_life && im.has_fini, "three lifecycle entries present" ); + CHECK( im.init_entry < im.life_entry && im.life_entry < im.fini_entry, "entry order in PROGRAM" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + __cpu_pc = im.init_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, ".init END returned" ); + CHECK( __cpu_get_stop_reason() == CPU_STOP_END, "INIT stop reason END" ); + CHECK( get64( 0 ) == 40, "init result" ); + __cpu_pc = im.life_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, "first LIFE END returned" ); + CHECK( get64( 0 ) == 41, "first life step" ); + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_EXIT, "resume reaches EXIT" ); + CHECK( __cpu_get_stop_reason() == CPU_STOP_EXIT, "EXIT stop reason" ); + CHECK( __cpu_get_exit_status() == 7, "EXIT status" ); + CHECK( get64( 0 ) == 42, "resume PC is after END" ); + __cpu_pc = im.fini_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, ".fini END returned" ); + CHECK( get64( 8 ) == 99, "fini result" ); + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu lifecycle ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-line-runner.sh b/tests/tst-line-runner.sh new file mode 100755 index 0000000..ed62877 --- /dev/null +++ b/tests/tst-line-runner.sh @@ -0,0 +1,174 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?CPU_AS is not set}" + +tmp="${TMPDIR:-/tmp}/libcpu-line-$$" +trap 'rm -rf "$tmp"' EXIT HUP INT TERM +mkdir -p "$tmp" + +# --help remains a compact public interface. Preprocessor source-control +# details belong to the documentation, not to the command-line synopsis. +"$CPU_AS" --help >"$tmp/help.txt" 2>&1 +grep -q -- '--help' "$tmp/help.txt" +grep -q -- '-o <file>' "$tmp/help.txt" +if grep -q -- '#line' "$tmp/help.txt"; then + echo "mcpu-as --help unexpectedly documents obsolete #line" >&2 + exit 1 +fi + +# The assembler accepts the usual UNIX-style option ordering. -c is no +# longer part of the interface because mcpu-as always produces ET_REL. +cat >"$tmp/cli.s" <<'SRC' +.text +.global helper +helper: + RET +SRC +"$CPU_AS" -o "$tmp/cli-before.o" "$tmp/cli.s" >/dev/null +"$CPU_AS" "$tmp/cli.s" -o "$tmp/cli-after.o" >/dev/null +test -s "$tmp/cli-before.o" +test -s "$tmp/cli-after.o" +cmp "$tmp/cli-before.o" "$tmp/cli-after.o" +if "$CPU_AS" -c "$tmp/cli.s" -o "$tmp/cli-old.o" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted obsolete -c" >&2 + exit 1 +fi +grep -Fq 'mcpu-as: unknown option -c' "$tmp/err.txt" + +# Native diagnostics count every ordinary physical source line, including a +# zero-length line. +cat >"$tmp/physical.s" <<'SRC' +.text + +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/physical.o" "$tmp/physical.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid physical-line source" >&2 + exit 1 +fi +grep -Fq "$tmp/physical.s:3: error:" "$tmp/err.txt" + +# Preprocessor line markers set the logical number and optional filename of the +# source line immediately following the marker. Trailing preprocessor flags +# are deliberately ignored by mcpu-as. +cat >"$tmp/marker.S" <<'SRC' +# 7 "included source.s" 1 3 4 anything-after-the-filename-is-ignored +.text +THIS_IS_NOT_AN_INSTRUCTION +SRC +if "$CPU_AS" -o "$tmp/marker.o" "$tmp/marker.S" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid marker source" >&2 + exit 1 +fi +grep -Fq 'included source.s:8: error:' "$tmp/err.txt" + +# The logical counter continues across empty ordinary lines. +cat >"$tmp/blank-lines.S" <<'SRC' +# 4 "dyn.s" + +.text +helper: +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/blank-lines.o" "$tmp/blank-lines.S" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid blank-line source" >&2 + exit 1 +fi +grep -Fq 'dyn.s:7: error:' "$tmp/err.txt" + +# Omitting the filename retains the previous logical filename. Leading zeroes +# remain decimal: 0200 means line 200, not octal 128. +cat >"$tmp/preserve.s" <<'SRC' +# 100 "first-name.s" +# 0200 +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/preserve.o" "$tmp/preserve.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid preserve test" >&2 + exit 1 +fi +grep -Fq 'first-name.s:201: error:' "$tmp/err.txt" + +# Zero is accepted. Here .text is logical line 0 and the bad opcode is line 1. +cat >"$tmp/zero.s" <<'SRC' +# 0 "zero.s" +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/zero.o" "$tmp/zero.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid zero-line test" >&2 + exit 1 +fi +grep -Fq 'zero.s:1: error:' "$tmp/err.txt" + +# The accepted decimal marker range remains 0..2147483647. +cat >"$tmp/range.s" <<'SRC' +# 2147483648 "too-large.s" +.text +SRC +if "$CPU_AS" -o "$tmp/range.o" "$tmp/range.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted an out-of-range line marker" >&2 + exit 1 +fi +grep -Fq 'range.s:1: error: preprocessor line marker value is outside 0..2147483647' "$tmp/err.txt" + +# #pragma and #lang are passed through by preprocessing stages for other +# consumers. mcpu-as ignores them and they do not advance the logical counter. +cat >"$tmp/meta.s" <<'SRC' +# 100 "meta.s" +#pragma once +#lang "mcpu" +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/meta.o" "$tmp/meta.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid pragma/lang source" >&2 + exit 1 +fi +grep -Fq 'meta.s:100: error:' "$tmp/err.txt" + +# Other '#' lines are discarded as assembler syntax but still count as normal +# logical source lines. +cat >"$tmp/hash.s" <<'SRC' +# 50 "hash.s" +#define SOMETHING 1 +#unknown whatever +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/hash.o" "$tmp/hash.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid generic-hash source" >&2 + exit 1 +fi +grep -Fq 'hash.s:52: error:' "$tmp/err.txt" + +# Historical #line is no longer a source-location directive. It is simply an +# unrecognized '#' line: ignored as syntax, counted as a normal source line, +# and it must not replace the diagnostic filename or line counter. +cat >"$tmp/obsolete-line.s" <<'SRC' +#line 700 "obsolete.s" +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/obsolete-line.o" "$tmp/obsolete-line.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid obsolete-#line source" >&2 + exit 1 +fi +grep -Fq "$tmp/obsolete-line.s:3: error:" "$tmp/err.txt" +if grep -Fq 'obsolete.s:' "$tmp/err.txt"; then + echo "obsolete #line unexpectedly changed source coordinates" >&2 + exit 1 +fi + +# A normal preprocessor marker is accepted independently of the input suffix. +cat >"$tmp/good.s" <<'SRC' +# 42 "generated/original source.s" 1 +.text +.global helper +helper: + RET +SRC +"$CPU_AS" -o "$tmp/good.o" "$tmp/good.s" >/dev/null +test -s "$tmp/good.o" + +echo "libmcpu mcpu-as preprocessor source-control regression: PASS" diff --git a/tests/tst-link-needed-runner.sh b/tests/tst-link-needed-runner.sh new file mode 100755 index 0000000..8f33986 --- /dev/null +++ b/tests/tst-link-needed-runner.sh @@ -0,0 +1,151 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-link-needed-$$ +HOME="$TMP/home" +export HOME +mkdir -p "$HOME/.mcpu/etc" "$TMP/root/lib" "$TMP/first" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/good.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/alt.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/bad.s" <<'EOS' +.global other +.text +other: + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/good.o" "$TMP/good.s" +"$CPU_AS" -o "$TMP/alt.o" "$TMP/alt.s" +"$CPU_AS" -o "$TMP/bad.o" "$TMP/bad.s" + +# The development link name is libfoo.so, but the ABI identity recorded in +# DT_NEEDED comes from the validated library SONAME. +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/root/lib/libfoo.so.1.2.3" "$TMP/good.o" +ln -s libfoo.so.1.2.3 "$TMP/root/lib/libfoo.so.1" +ln -s libfoo.so.1 "$TMP/root/lib/libfoo.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lfoo -o "$TMP/main" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main" > "$TMP/main.dynamic" +grep -q 'NEEDED.*\[libfoo.so.1\]' "$TMP/main.dynamic" +[ "`grep -c 'NEEDED' "$TMP/main.dynamic"`" -eq 1 ] +if grep -q 'libfoo.so.1.2.3' "$TMP/main.dynamic"; then + echo "DT_NEEDED unexpectedly contains physical library filename" >&2 + exit 1 +fi + +# The separated -l NAME spelling is equivalent. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L "$TMP/root/lib" -l foo -o "$TMP/main-separated" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-separated" | grep -q 'NEEDED.*\[libfoo.so.1\]' + +# Repeated requests resolving to the same SONAME produce one dependency. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lfoo -lfoo -o "$TMP/main-duplicate" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-duplicate" > "$TMP/main-duplicate.dynamic" +[ "`grep -c 'NEEDED' "$TMP/main-duplicate.dynamic"`" -eq 1 ] + +# -L order is real link-name search order. The selected object's SONAME, +# not the requested link name, is recorded. +"$CPU_LD" --shared --soname=libfoo-alt.so.7 \ + -o "$TMP/first/libfoo.so" "$TMP/alt.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/first" -L"$TMP/root/lib" -lfoo \ + -o "$TMP/main-first" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-first" | grep -q 'NEEDED.*\[libfoo-alt.so.7\]' + +# The configured system library tree is also used by -l. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/root/lib; +EOC +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -lfoo -o "$TMP/main-config" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-config" | grep -q 'NEEDED.*\[libfoo.so.1\]' + +# A shared object may itself record a direct ABI-v3 dependency. +"$CPU_LD" --shared --soname=libchild.so.1 \ + -lfoo -o "$TMP/libchild.so.1.0.0" "$TMP/bad.o" +"$CPU_READELF" -d "$TMP/libchild.so.1.0.0" > "$TMP/child.dynamic" +grep -q 'SONAME.*\[libchild.so.1\]' "$TMP/child.dynamic" +grep -q 'NEEDED.*\[libfoo.so.1\]' "$TMP/child.dynamic" + +# A named dependency must exist and must be a valid ABI-v3 ET_DYN object. +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lmissing -o "$TMP/missing" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "missing -l dependency unexpectedly linked" >&2 + exit 1 +fi + +cp "$TMP/main" "$TMP/root/lib/libexec.so" +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lexec -o "$TMP/from-exec" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "ET_EXEC unexpectedly accepted as -l provider" >&2 + exit 1 +fi + +# Existing file, valid shared object, wrong exports: link must fail before +# producing an executable. +"$CPU_LD" --shared --soname=libbad.so.1 \ + -o "$TMP/root/lib/libbad.so" "$TMP/bad.o" +rm -f "$TMP/no-provider" +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lbad -o "$TMP/no-provider" "$TMP/main.o" \ + >"$TMP/no-provider.out" 2>"$TMP/no-provider.err"; then + echo "library without required export unexpectedly linked" >&2 + exit 1 +fi +grep -q 'undefined symbol not provided by linked libraries: foo' \ + "$TMP/no-provider.err" +[ ! -e "$TMP/no-provider" ] + +# ABI-v1 -l searches only for a static archive. This directory contains +# only libfoo.so, so the static link must fail instead of using ET_DYN. +if "$CPU_LD" -L"$TMP/root/lib" -lfoo -o "$TMP/static" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "ABI-v1 unexpectedly selected a shared -l provider" >&2 + exit 1 +fi + +# 0.2.16 resolves the recorded SONAME at runtime through the same system +# library search policy. No explicit --ldpreload is required. +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +"$CPU_LD" --help 2>&1 | grep -q -- '-l <name>' + +exit 0 diff --git a/tests/tst-melf.c b/tests/tst-melf.c new file mode 100644 index 0000000..c872857 --- /dev/null +++ b/tests/tst-melf.c @@ -0,0 +1,430 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-asm.h" +#include "mcpu-object.h" +#include "mcpu-link.h" +#include "mcpu-exec.h" +#include "mcpu-melf.h" + +#define CHECK(COND, MSG) \ + do { if(!(COND)) { fprintf(stderr, "FAIL: %s\n", (MSG)); goto out; } } while(0) + +static int +find_symbol( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; ++i ) + if( !strcmp( o->symbols[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +write_copy( const char *path, const unsigned char *p, size_t n ) +{ + FILE *f = fopen( path, "wb" ); + if( !f ) return( -1 ); + if( n && fwrite( p, 1, n, f ) != n ) + { + fclose( f ); + return( -1 ); + } + return( fclose( f ) ? -1 : 0 ); +} + +static int +expect_load_fail( const char *path, const char *needle ) +{ + cpu_exec_loaded ld; + char err[512]; + char *av[] = {(char *)"prog"}; + + if( !cpu_exec_load( path, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "malformed executable unexpectedly loaded: %s\n", path ); + return( -1 ); + } + if( needle && !strstr( err, needle ) ) + { + fprintf( stderr, "load error mismatch: expected '%s', got '%s'\n", needle, err ); + return( -1 ); + } + return( 0 ); +} + +int +main( void ) +{ + static const char main_src[] = + ".extern func\n" + ".extern shared\n" + ".extern scratch\n" + ".text\n" + "_start_life:\n" + " LDI r64@0x00000, 40\n" + " CALL func\n" /* CODE64 */ + " STORE shared, r64@0x00000\n" /* DATA64 */ + " LOAD r64@0x00018, shared\n" /* DATA64 */ + " LEA r64@0x00020, scratch\n" /* PTR64_CODE */ + " LDI r32@0x00030, 0\n" + " EXIT r32@0x00030\n"; + + static const char lib_src[] = + ".global func, shared, scratch, shared_ptr, func_ptr\n" + ".data\n" + ".align 32\n" + "shared:\n" + " .integer 64, 0\n" + "shared_ptr:\n" + " .ptr scratch\n" /* PTR64_DATA */ + "func_ptr:\n" + " .cptr func\n" /* CPTR64_DATA */ + ".bss\n" + ".align 64\n" + "scratch:\n" + " .zero 128\n" + ".text\n" + "func:\n" + " LDI r64@0x00008, 2\n" + " IADD r64@0x00000, r64@0x00000, r64@0x00008\n" + " RET\n"; + + char dtemplate[] = "/tmp/libcpu-melf-XXXXXX"; + char main_o[512], lib_o[512], exe[512], life_exe[512], bad_guard[512], bad_entry[512]; + char err[512]; + cpu_asm_object built[2], readback[2]; + cpu_asm_image im; + cpu_exec_options opt; + cpu_exec_loaded ld; + unsigned char *file = 0; + size_t file_size = 0; + size_t ix, i; + __mpu_uint64_t p; + int data; + int rc = 1; + + memset( built, 0, sizeof( built ) ); + memset( readback, 0, sizeof( readback ) ); + memset( &im, 0, sizeof( im ) ); + + CHECK( mkdtemp( dtemplate ) != NULL, "mkdtemp" ); + snprintf( main_o, sizeof( main_o ), "%s/main.o", dtemplate ); + snprintf( lib_o, sizeof( lib_o ), "%s/lib.o", dtemplate ); + snprintf( exe, sizeof( exe ), "%s/a.melf", dtemplate ); + snprintf( life_exe, sizeof( life_exe ), "%s/lifecycle.melf", dtemplate ); + snprintf( bad_guard, sizeof( bad_guard ), "%s/bad-guard.melf", dtemplate ); + snprintf( bad_entry, sizeof( bad_entry ), "%s/bad-entry.melf", dtemplate ); + + __cpu_init(); + + /* + * LibMPU uses MPU_BYTE_ORDER / MPU_WORD_ORDER as numeric tags (for + * example 1234), while the *_LITTLE_ENDIAN / *_BIG_ENDIAN macros are + * boolean selectors. MELF must key off the boolean selectors rather + * than compare the tag with 0/1. + */ +#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1 + CHECK( cpu_melf_data_encoding() == MELF_DATA_LSB, + "MELF EI_DATA follows LibMPU little-endian selector"); +#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1 + CHECK( cpu_melf_data_encoding() == MELF_DATA_MSB, + "MELF EI_DATA follows LibMPU big-endian selector"); +#else +# error Unsupported LibMPU byte order for MELF regression +#endif + { + MElf_McpuTarget mt; + cpu_melf_fill_target( &mt ); +#if CPU_DATA_WORD_ORDER_LITTLE_ENDIAN == 1 + CHECK( mt.mt_data_word_order == MELF_MCPU_WORD_ORDER_LSB, + "MELF target metadata follows LibMPU little word-order selector"); +#elif CPU_DATA_WORD_ORDER_BIG_ENDIAN == 1 + CHECK( mt.mt_data_word_order == MELF_MCPU_WORD_ORDER_MSB, + "MELF target metadata follows LibMPU big word-order selector"); +#else +# error Unsupported LibMPU word order for MELF regression +#endif + } + + CHECK( cpu_assemble_object( main_src, &built[0], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_assemble_object( lib_src, &built[1], err, sizeof( err ) ) == 0, err ); + CHECK( built[1].has_bss && built[1].bss_size == 128 && built[1].bss_align == 64, + "assembler .bss/NOBITS metadata"); + CHECK( built[0].reloc_count == 4 && built[1].reloc_count == 2, + "all expected relocations emitted"); + + /* Assembler-only lifecycle pseudo-label rules. */ + { + cpu_asm_object q; + memset( &q, 0, sizeof( q ) ); + CHECK( cpu_assemble_object( + ".init\n_start:\n END\n.text\n_start_life:\n END\n", + &q, err, sizeof( err )) != 0 && strstr( err, "_start_life" ), + "_start_life is rejected when .init exists"); + cpu_asm_object_free( &q ); + memset( &q, 0, sizeof( q ) ); + CHECK( cpu_assemble_object( + ".text\n_start:\n_start_life:\n END\n", + &q, err, sizeof( err )) != 0 && strstr( err, "conflicts" ), + "_start_life conflicts with explicit _start/_life"); + cpu_asm_object_free( &q ); + memset( &q, 0, sizeof( q ) ); + CHECK( cpu_assemble_object( + ".weak _start\n.text\n_start_life:\n END\n", + &q, err, sizeof( err )) != 0 && strstr( err, "cannot be weak" ), + "lifecycle symbols cannot be weak"); + cpu_asm_object_free( &q ); + } + + /* Full .init/.text/.fini ET_EXEC lifecycle metadata and loader path. */ + { + static const char life_src[] = + ".init\n" + "_start:\n" + " END\n" + ".text\n" + "_life:\n" + " LDI r32@0x00000, 0\n" + " EXIT r32@0x00000\n" + ".fini\n" + "_fini:\n" + " END\n"; + cpu_asm_object q; + cpu_asm_image qi; + cpu_exec_loaded ql; + cpu_exec_options qo = {512, 0, 0, 0}; + char *qv[] = {(char *)"lifecycle.melf"}; + memset( &q, 0, sizeof( q ) ); + memset( &qi, 0, sizeof( qi ) ); + memset( &ql, 0, sizeof( ql ) ); + CHECK( cpu_assemble_object( life_src, &q, err, sizeof( err ) ) == 0, err ); + CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) == 0, err ); + CHECK( qi.has_start && qi.has_life && qi.has_init && qi.has_fini && + qi.start_entry == qi.init_entry && qi.start_entry != qi.life_entry, + "linked INIT/LIFE/FINI lifecycle"); + CHECK( cpu_exec_write( life_exe, &qi, &qo, err, sizeof( err ) ) == 0, err ); + CHECK( cpu_exec_load( life_exe, 1, qv, &ql, err, sizeof( err ) ) == 0, err ); + CHECK( ql.has_init && ql.has_fini && ql.start_entry == ql.init_entry && + ql.start_entry != ql.life_entry, + "ET_EXEC INIT/LIFE/FINI loader metadata"); + __cpu_free_context(); + __cpu_init(); + cpu_asm_image_free( &qi ); + cpu_asm_object_free( &q ); + } + + CHECK( find_symbol( &built[0], "_start", &ix ) == 0, + "_start_life emits _start"); + CHECK( built[0].symbols[ix].binding == CPU_OBJ_BIND_GLOBAL && + built[0].symbols[ix].type == CPU_OBJ_TYPE_FUNC, + "_start ABI symbol attributes"); + { + __mpu_uint64_t sv = built[0].symbols[ix].value; + CHECK( find_symbol( &built[0], "_life", &ix ) == 0, + "_start_life emits _life"); + CHECK( built[0].symbols[ix].value == sv && + built[0].symbols[ix].binding == CPU_OBJ_BIND_GLOBAL && + built[0].symbols[ix].type == CPU_OBJ_TYPE_FUNC, + "_start/_life have same GLOBAL/FUNC address"); + } + + CHECK( cpu_object_write( main_o, &built[0], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_object_write( lib_o, &built[1], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_object_read( main_o, &readback[0], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_object_read( lib_o, &readback[1], err, sizeof( err ) ) == 0, err ); + CHECK( readback[1].has_bss && readback[1].bss_size == 128 && + readback[1].bss_align == 64, "ET_REL .bss round trip"); + CHECK( readback[0].reloc_count == 4 && readback[1].reloc_count == 2, + "ET_REL RELA round trip"); + + /* Undefined lifecycle declarations retain STT_FUNC in ET_REL. */ + { + cpu_asm_object q; + memset( &q, 0, sizeof( q ) ); + CHECK( cpu_assemble_object( + ".extern _fini\n.text\n_start_life:\n END\n", + &q, err, sizeof(err)) == 0, err); + CHECK( find_symbol( &q, "_fini", &ix ) == 0 && + q.symbols[ix].section == CPU_OBJ_SEC_UNDEF && + q.symbols[ix].binding == CPU_OBJ_BIND_GLOBAL && + q.symbols[ix].type == CPU_OBJ_TYPE_FUNC, + "undefined lifecycle symbol is GLOBAL/FUNC"); + cpu_asm_object_free( &q ); + } + + /* WEAK resolution rules fixed by the symbol/RELA ABI. */ + { + static const char weak_ptr_src[] = + ".weak missing\n" + ".data\nwp:\n .ptr missing\n" + ".text\n_start_life:\n END\n"; + cpu_asm_object q; + cpu_asm_image qi; + memset( &q, 0, sizeof( q ) ); + memset( &qi, 0, sizeof( qi ) ); + CHECK( cpu_assemble_object( weak_ptr_src, &q, err, sizeof( err ) ) == 0, err ); + CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) == 0, err ); + CHECK( qi.data_size >= CPU_DATA_NULL_GUARD_SIZE + 8 && + __cpu_data_get_u64( qi.data + CPU_DATA_NULL_GUARD_SIZE ) == 0, + "unresolved weak pointer relocation becomes NULL"); + cpu_asm_image_free( &qi ); + cpu_asm_object_free( &q ); + } + { + static const char weak_code_src[] = + ".weak missing\n" + ".text\n_start_life:\n CALL missing\n END\n"; + cpu_asm_object q; + cpu_asm_image qi; + memset( &q, 0, sizeof( q ) ); + memset( &qi, 0, sizeof( qi ) ); + CHECK( cpu_assemble_object( weak_code_src, &q, err, sizeof( err ) ) == 0, err ); + CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) != 0 && + strstr( err, "unresolved weak" ), + "unresolved weak CODE64 is a link error"); + cpu_asm_image_free( &qi ); + cpu_asm_object_free( &q ); + } + { + static const char caller_src[] = + ".extern foo\n" + ".text\n_start_life:\n CALL foo\n END\n"; + static const char weak_def_src[] = + ".weak foo\n.text\nfoo:\n NOP\n RET\n"; + static const char strong_def_src[] = + ".global foo\n.text\nfoo:\n RET\n"; + cpu_asm_object q[3]; + cpu_asm_image qi; + __mpu_uint64_t expected_strong, encoded; + memset( q, 0, sizeof( q ) ); + memset( &qi, 0, sizeof( qi ) ); + CHECK( cpu_assemble_object( caller_src, &q[0], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_assemble_object( weak_def_src, &q[1], err, sizeof( err ) ) == 0, err ); + CHECK( cpu_assemble_object( strong_def_src, &q[2], err, sizeof( err ) ) == 0, err ); + expected_strong = q[0].text_size + q[1].text_size; + CHECK( cpu_link_objects( q, 3, &qi, err, sizeof( err ) ) == 0, err ); + CHECK( q[0].reloc_count == 1 && q[0].relocs[0].type == CPU_RELOC_CODE64, + "strong/weak test CALL relocation"); + encoded = __cpu_code_get_u64( qi.code + q[0].relocs[0].offset ); + CHECK( encoded == expected_strong, "strong GLOBAL overrides earlier WEAK definition" ); + cpu_asm_image_free( &qi ); + for( i = 0; i < 3; ++i ) cpu_asm_object_free( &q[i] ); + } + + CHECK( cpu_link_objects( readback, 2, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.has_start && im.has_life && !im.has_init && + im.start_entry == im.life_entry, "linked no-INIT lifecycle"); + CHECK( im.data_size == 56 && im.data_mem_size == 192, + "DATA filesz/memsz including .bss"); + CHECK( im.bss_present && im.bss_offset == 64 && im.bss_size == 128, + "final .bss placement"); + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i ) + CHECK( im.data[i] == 0, "linker-owned DATA NULL guard" ); + + p = __cpu_data_get_u64( im.data + 40 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA && + __cpu_ptr_offset( p ) == 64, "PTR64_DATA relocation to .bss"); + p = __cpu_data_get_u64( im.data + 48 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_PROGRAM, + "CPTR64_DATA relocation"); + + opt.stack_size = 512; + opt.have_heap = 1; + opt.heap_size = 128; + opt.heap_limit = 1024; + CHECK( cpu_exec_write( exe, &im, &opt, err, sizeof( err ) ) == 0, err ); + + { + char *av[] = {(char *)"a.melf", (char *)"alpha", (char *)"beta"}; + CHECK( cpu_exec_load( exe, 3, av, &ld, err, sizeof( err ) ) == 0, err ); + } + CHECK( __cpu_program_size() == im.code_size, "PROGRAM load size" ); + CHECK( __cpu_data_size() == 192, "DATA p_memsz load size" ); + CHECK( __cpu_stack_size() == 512, "STACK size comes from PT_MCPU_STACK" ); + CHECK( __cpu_heap_size() == 128 && __cpu_heap_limit() == 1024, + "HEAP initial/maximum sizes"); + CHECK( __cpu_sp == ld.startup_base && __cpu_fp == ld.stack_size, + "initial SP/FP startup state"); + CHECK( __cpu_data_get_u32( __cpu_stack_ptr() + ld.startup_base + + MCPU_STARTUP_VERSION_OFF) == MCPU_STARTUP_VERSION_CURRENT, + "McpuStartup.version"); + CHECK( __cpu_data_get_u64( __cpu_stack_ptr() + ld.startup_base + + MCPU_STARTUP_ARGC_OFF) == 3, + "McpuStartup.argc"); + p = __cpu_data_get_u64( __cpu_stack_ptr() + ld.startup_base + + MCPU_STARTUP_ARGV_OFF); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_STACK, + "McpuStartup.argv segment"); + CHECK( __cpu_data_get_u64( __cpu_stack_ptr() + __cpu_ptr_offset( p ) + 3 * 8 ) == 0, + "argv[argc] == NULL"); + for( i = 64; i < 192; ++i ) + CHECK( __cpu_data_ptr()[i] == 0, "NOBITS .bss loads as zero" ); + + /* Negative loader checks: it validates, never repairs. */ + CHECK( cpu_melf_readall( exe, &file, &file_size, err, sizeof( err ) ) == 0, err ); + data = file[MELF_EI_DATA]; + { + __mpu_uint64_t phoff = cpu_melf_get64( file + MELF64_EHDR_E_PHOFF_OFF, data ); + __mpu_uint16_t phnum = cpu_melf_get16( file + MELF64_EHDR_E_PHNUM_OFF, data ); + __mpu_uint64_t dataoff = UINT64_MAX; + for( i = 0; i < phnum; ++i ) + { + unsigned char *q = file + (size_t)phoff + i * MELF64_PHDR_SIZE; + if( cpu_melf_get32( q + MELF64_PHDR_P_TYPE_OFF, data ) == MELF_PT_LOAD && + MELF_MCPU_PTR64_SEG( cpu_melf_get64( q + MELF64_PHDR_P_VADDR_OFF, data ) ) == + MELF_MCPU_SEG_DATA) + { + dataoff = cpu_melf_get64( q + MELF64_PHDR_P_OFFSET_OFF, data ); + break; + } + } + CHECK( dataoff != UINT64_MAX && dataoff < file_size, "find DATA PT_LOAD" ); + file[(size_t)dataoff] = 1; + CHECK( write_copy( bad_guard, file, file_size ) == 0, "write bad guard file" ); + file[(size_t)dataoff] = 0; + } + CHECK( expect_load_fail( bad_guard, "DATA NULL guard" ) == 0, + "loader rejects corrupted DATA guard"); + + cpu_melf_put64( file + MELF64_EHDR_E_ENTRY_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, 1 ), data); + CHECK( write_copy( bad_entry, file, file_size ) == 0, "write bad entry file" ); + CHECK( expect_load_fail( bad_entry, "without INIT" ) == 0, + "loader rejects inconsistent lifecycle entries"); + + rc = 0; +out: + free( file ); + cpu_asm_image_free( &im ); + cpu_asm_object_free( &built[0] ); + cpu_asm_object_free( &built[1] ); + cpu_asm_object_free( &readback[0] ); + cpu_asm_object_free( &readback[1] ); + __cpu_free_context(); + if( dtemplate[0] ) + { + unlink( main_o ); + unlink( lib_o ); + unlink( exe ); + unlink( life_exe ); + unlink( bad_guard ); + unlink( bad_entry ); + rmdir( dtemplate ); + } + if( !rc ) puts( "libcpu MELF ET_REL/ET_EXEC ABI regression: PASS" ); + return( rc ); +} diff --git a/tests/tst-memory.c b/tests/tst-memory.c new file mode 100644 index 0000000..ee31477 --- /dev/null +++ b/tests/tst-memory.c @@ -0,0 +1,85 @@ +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <libmcpu.h> + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static int +page_aligned( const void *p, size_t page ) +{ + return( p != 0 && ( (size_t)p % page ) == 0 ); +} + +int +main( void ) +{ + long ps; + size_t page; + pid_t pid; + int status; + __mpu_UNIT_t code[3] = + { + 0x00, 0x00, 0x00 + }; + + __cpu_init(); + ps = sysconf( _SC_PAGESIZE ); + CHECK( ps > 0, "host page size" ); + page = (size_t)ps; + + CHECK( CPU_ABI_MAX_ALIGN == 16U, "ABI maximum natural alignment" ); + CHECK( CPU_ABI_STACK_ALIGN == 16U, "ABI stack alignment" ); + CHECK( CPU_ABI_ARGUMENT_BLOCK_ALIGN == 16U, "ABI argument block alignment" ); + CHECK( CPU_DATA_NULL_GUARD_SIZE == 16U, "DATA NULL guard size" ); + CHECK( CPU_NULL_PTR == __mpu_UINT64_C( 0 ), "NULL PTR64 encoding" ); + + CHECK( __cpu_data_alloc( 1 ) == 0, "allocate one-byte DATA" ); + CHECK( __cpu_data_size() == 1, "DATA architectural size is exact" ); + CHECK( page_aligned( __cpu_data_ptr(), page ), "DATA host base is page aligned" ); + CHECK( __cpu_data_ptr()[0] == 0, "anonymous DATA mapping is zero-filled" ); + __cpu_data_ptr()[0] = 0x5a; + CHECK( __cpu_data_ptr()[0] == 0x5a, "DATA mapping is writable" ); + + CHECK( __cpu_stack_alloc( 1 ) == 0, "allocate one-byte STACK" ); + CHECK( __cpu_stack_size() == 1, "STACK architectural size is exact" ); + CHECK( page_aligned( __cpu_stack_ptr(), page ), "STACK host base is page aligned" ); + CHECK( __cpu_stack_ptr()[0] == 0, "anonymous STACK mapping is zero-filled" ); + CHECK( __cpu_sp == 1 && __cpu_fp == 1, "empty STACK uses exact architectural size" ); + + CHECK( __cpu_program_alloc( sizeof( code ) ) == 0, "allocate PROGRAM for loading" ); + CHECK( __cpu_program_size() == sizeof( code ), "PROGRAM architectural size is exact" ); + CHECK( page_aligned( __cpu_program_ptr(), page ), "PROGRAM host base is page aligned" ); + memcpy( __cpu_program_ptr(), code, sizeof( code ) ); + __cpu_program_ptr()[0] = 0x12; + /* PROGRAM is writable during image loading. */ + CHECK( __cpu_program_ptr()[0] == 0x12, "PROGRAM writable before protect" ); + CHECK( __cpu_program_protect() == 0, "protect PROGRAM read-only" ); + + /* Verify host protection without crashing the test process itself. */ + pid = fork(); + CHECK( pid >= 0, "fork for PROGRAM protection test" ); + if( pid == 0 ) + { + volatile __mpu_UNIT_t *p = __cpu_program_ptr(); + p[0] = 0x34; + _exit( 0 ); + } + CHECK( waitpid( pid, &status, 0 ) == pid, "wait for protection child" ); + CHECK( WIFSIGNALED( status ), "PROGRAM host write is trapped" ); + CHECK( WTERMSIG( status ) == SIGSEGV || WTERMSIG( status ) == SIGBUS, + "PROGRAM write fault signal"); + + /* Reallocation must unmap the protected image and create a fresh RW mapping. */ + CHECK( __cpu_program_alloc( 2 ) == 0, "reallocate PROGRAM after protection" ); + CHECK( __cpu_program_size() == 2, "reallocated PROGRAM exact size" ); + __cpu_program_ptr()[0] = 0x56; + CHECK( __cpu_program_ptr()[0] == 0x56, "new PROGRAM mapping writable for loading" ); + + __cpu_free_context(); + puts( "libcpu anonymous-mmap memory regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-model-segments.c b/tests/tst-model-segments.c new file mode 100644 index 0000000..686bee9 --- /dev/null +++ b/tests/tst-model-segments.c @@ -0,0 +1,108 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".control\n" +"ctl:\n" +" .integer 64, 10\n" +".input\n" +"stimulus:\n" +" .integer 64, 32\n" +".output\n" +"answer:\n" +" .integer 64, 0\n" +".trace\n" +"sample:\n" +" .integer 64, 0\n" +".text\n" +"_life:\n" +" LEA r64@0x00000, ctl\n" +" LEA r64@0x00008, stimulus\n" +" LEA r64@0x00010, answer\n" +" LEA r64@0x00018, sample\n" +" LOADP r64@0x00020, r64@0x00000\n" +" LOADP r64@0x00028, r64@0x00008\n" +" IADD r64@0x00030, r64@0x00020, r64@0x00028\n" +" STOREP r64@0x00010, r64@0x00030\n" +" STOREP r64@0x00018, r64@0x00030\n" +" HALT\n"; + +static __mpu_uint64_t +r64( unsigned a ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, a ) ) ); +} + +static void +regop( cpu_operand *o, unsigned bits, unsigned addr ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = addr; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction x; + char err[256]; + __mpu_uint64_t p; + int rc; + __cpu_init(); + CHECK( CPU_PTR_SEG_CONTROL == 3, "CONTROL selector" ); + CHECK( CPU_PTR_SEG_INPUT == 4, "INPUT selector" ); + CHECK( CPU_PTR_SEG_OUTPUT == 5, "OUTPUT selector" ); + CHECK( CPU_PTR_SEG_TRACE == 6, "TRACE selector" ); + CHECK( CPU_PTR_SEG_HEAP == 7, "HEAP selector" ); + CHECK( CPU_PTR_SEG_FIRST_RESERVED == 8, "reserved selectors begin at 8" ); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.control_size == 8 && im.input_size == 8 && im.output_size == 8 && im.trace_size == 8, "model segment image sizes" ); + CHECK( im.has_life && im.life_entry == 0, "_life entry metadata" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_control_set( im.control, im.control_size ) == 0, "control set" ); + CHECK( __cpu_input_set( im.input, im.input_size ) == 0, "input set" ); + CHECK( __cpu_output_set( im.output, im.output_size ) == 0, "output set" ); + CHECK( __cpu_trace_set( im.trace, im.trace_size ) == 0, "trace set" ); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + CHECK( __cpu_heap_alloc( 128, 1024 ) == 0, "heap alloc" ); + __cpu_pc = im.life_entry; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "segment program run" ); + CHECK( __cpu_ptr_segment( r64( 0x00 ) ) == CPU_PTR_SEG_CONTROL, "LEA CONTROL" ); + CHECK( __cpu_ptr_segment( r64( 0x08 ) ) == CPU_PTR_SEG_INPUT, "LEA INPUT" ); + CHECK( __cpu_ptr_segment( r64( 0x10 ) ) == CPU_PTR_SEG_OUTPUT, "LEA OUTPUT" ); + CHECK( __cpu_ptr_segment( r64( 0x18 ) ) == CPU_PTR_SEG_TRACE, "LEA TRACE" ); + CHECK( __cpu_data_get_u64( __cpu_output_ptr() ) == 42, "OUTPUT guest write" ); + CHECK( __cpu_data_get_u64( __cpu_trace_ptr() ) == 42, "TRACE guest write" ); + + memset( &x, 0, sizeof( x ) ); + x.opcode = CPU_OP_STOREP; + regop( &x.operand[0], 64, 0x00 ); + regop( &x.operand[1], 64, 0x30 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "CONTROL is guest read-only" ); + regop( &x.operand[0], 64, 0x08 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "INPUT is guest read-only" ); + + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0, "make HEAP pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + regop( &x.operand[0], 64, 0x38 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "HEAP is protected above initial _end break" ); + CHECK( __cpu_heap_size() == 128 && __cpu_heap_limit() == 1024, "HEAP size/limit ABI" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_HEAP, 0 ) == 0, "make HEAP guard pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "HEAP low guard rejects access" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_FIRST_RESERVED, 0 ) == 0, "make reserved pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + rc = __cpu_exec( &x ); + CHECK( rc == CPU_EXEC_POINTER_FAULT, "reserved selector still faults" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu model-segment/HEAP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-mpu-coverage.c b/tests/tst-mpu-coverage.c new file mode 100644 index 0000000..0c8e3a3 --- /dev/null +++ b/tests/tst-mpu-coverage.c @@ -0,0 +1,580 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +static int failures; +#define CHECK(c,m) do { if(!(c)){ fprintf(stderr,"FAIL: %s\n",(m)); ++failures; } } while(0) + +static cpu_operand +R( unsigned bits, __mpu_uint32_t addr ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = addr; + return( o ); +} + +static void +I( cpu_instruction *i, cpu_opcode op ) +{ + memset( i, 0, sizeof( *i ) ); + i->opcode = op; +} + +static __mpu_UNIT_t * +P( unsigned bits, __mpu_uint32_t addr ) +{ + return( __cpu_reg_ptr( bits, addr ) ); +} + +static void +put64( unsigned addr, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( P( 64, addr ), v ); +} + +static __mpu_uint64_t +get64( unsigned addr ) +{ + return( __cpu_data_get_u64( P( 64, addr ) ) ); +} + +static __mpu_int64_t +geti64( unsigned addr ) +{ + __mpu_uint64_t u = get64( addr ); + __mpu_int64_t v; + memcpy( &v, &u, 8 ); + return( v ); +} + +static void +setreal( unsigned bits, unsigned addr, const char *s ) +{ + int nb = (int)( bits / 8U ); + CHECK( ascii_to_real( ( mpu_real * ) P( bits, addr ), (__mpu_char8_t *)s, nb ) >= 0, "ascii_to_real" ); +} + +int +main( void ) +{ + cpu_instruction x; + __cpu_init(); + + /* Integer shifts/rotates: compare CPU output with the matching LibMPU API. */ + { + struct S + { + cpu_opcode op; + void( *fn )( mpu_int *, mpu_int *, int ); + const char *n; + } + v[] = + { + {CPU_OP_ISHL,ishl,"ISHL"},{CPU_OP_ISHR,ishr,"ISHR"},{CPU_OP_ISAL,isal,"ISAL"},{CPU_OP_ISAR,isar,"ISAR"}, + {CPU_OP_IROL,irol,"IROL"},{CPU_OP_IROR,iror,"IROR"},{CPU_OP_IRCL,ircl,"IRCL"},{CPU_OP_IRCR,ircr,"IRCR"} + }; + unsigned k; + mpu_int8_t ref, src; + for( k = 0; k < sizeof( v ) / sizeof( v[0] ); k++ ) + { + src[0] = 0x81; + ref[0] = 0; + if( v[k].op == CPU_OP_IRCL || v[k].op == CPU_OP_IRCR ) __mpu_stc(); + else __mpu_clc(); + v[k].fn( ( mpu_int * ) ref, ( mpu_int * ) src, 1 ); + *P( 8, 0x100 ) = 0x81; + *P( 8, 0x101 ) = 0; + if( v[k].op == CPU_OP_IRCL || v[k].op == CPU_OP_IRCR ) __mpu_stc(); + else __mpu_clc(); + I( &x, v[k].op ); + x.operand[0] = R( 8, 0x101 ); + x.operand[1] = R( 8, 0x100 ); + CHECK( __cpu_exec( &x ) == 0, v[k].n ); + CHECK( *P( 8, 0x101 ) == ref[0], v[k].n ); + } + } + { + struct N + { + cpu_opcode op; + void( *fn )( mpu_int *, mpu_int *, unsigned int, int ); + const char *n; + } + v[] = + { + {CPU_OP_ISHLN,ishln,"ISHLN"},{CPU_OP_ISHRN,ishrn,"ISHRN"},{CPU_OP_ISALN,isaln,"ISALN"},{CPU_OP_ISARN,isarn,"ISARN"}, + {CPU_OP_IROLN,iroln,"IROLN"},{CPU_OP_IRORN,irorn,"IRORN"},{CPU_OP_IRCLN,ircln,"IRCLN"},{CPU_OP_IRCRN,ircrn,"IRCRN"} + }; + unsigned k; + mpu_int16_t ref, src; + for( k = 0; k < sizeof( v ) / sizeof( v[0] ); k++ ) + { + __cpu_data_put_u16( src, 0x8123 ); + if( v[k].op == CPU_OP_IRCLN || v[k].op == CPU_OP_IRCRN ) __mpu_stc(); + else __mpu_clc(); + v[k].fn( ( mpu_int * ) ref, ( mpu_int * ) src, 3, 2 ); + __cpu_data_put_u16( P( 16, 0x120 ), 0x8123 ); + put64( 0x128, 3 ); + if( v[k].op == CPU_OP_IRCLN || v[k].op == CPU_OP_IRCRN ) __mpu_stc(); + else __mpu_clc(); + I( &x, v[k].op ); + x.operand[0] = R( 16, 0x122 ); + x.operand[1] = R( 16, 0x120 ); + x.operand[2] = R( 64, 0x128 ); + CHECK( __cpu_exec( &x ) == 0, v[k].n ); + CHECK( memcmp( P( 16, 0x122 ), ref, 2 ) == 0, v[k].n ); + } + } + + /* INC/DEC/XCHG and width-changing integer copy/conversion. */ + *P( 8, 0x140 ) = 41; + I( &x, CPU_OP_IINC ); + x.operand[0] = R( 8, 0x141 ); + x.operand[1] = R( 8, 0x140 ); + CHECK( __cpu_exec( &x ) == 0, "IINC" ); + CHECK( *P( 8, 0x141 ) == 42, "IINC result" ); + I( &x, CPU_OP_IDEC ); + x.operand[0] = R( 8, 0x142 ); + x.operand[1] = R( 8, 0x141 ); + CHECK( __cpu_exec( &x ) == 0, "IDEC" ); + CHECK( *P( 8, 0x142 ) == 41, "IDEC result" ); + *P( 8, 0x143 ) = 0x12; + *P( 8, 0x144 ) = 0x34; + I( &x, CPU_OP_IXCHG ); + x.operand[0] = R( 8, 0x143 ); + x.operand[1] = R( 8, 0x144 ); + CHECK( __cpu_exec( &x ) == 0, "IXCHG" ); + CHECK( *P( 8, 0x143 ) == 0x34 && *P( 8, 0x144 ) == 0x12, "IXCHG result" ); + *P( 8, 0x145 ) = 0xff; + I( &x, CPU_OP_ICPY ); + x.operand[0] = R( 16, 0x146 ); + x.operand[1] = R( 8, 0x145 ); + CHECK( __cpu_exec( &x ) == 0, "ICPY" ); + CHECK( __cpu_data_get_u16( P( 16, 0x146 ) ) == 255, "ICPY zero extension" ); + I( &x, CPU_OP_ICVT ); + x.operand[0] = R( 16, 0x148 ); + x.operand[1] = R( 8, 0x145 ); + CHECK( __cpu_exec( &x ) == 0, "ICVT" ); + CHECK( __cpu_data_get_u16( P( 16, 0x148 ) ) == 0xffff, "ICVT sign extension" ); + + /* Full products and quotient+remainder forms. */ + __cpu_data_put_u16( P( 16, 0x160 ), 300 ); + __cpu_data_put_u16( P( 16, 0x162 ), 200 ); + I( &x, CPU_OP_IMUL ); + x.operand[0] = R( 32, 0x164 ); + x.operand[1] = R( 16, 0x160 ); + x.operand[2] = R( 16, 0x162 ); + CHECK( __cpu_exec( &x ) == 0, "IMUL" ); + CHECK( __cpu_data_get_u32( P( 32, 0x164 ) ) == 60000, "IMUL result" ); + __cpu_data_put_u16( P( 16, 0x168 ), (__mpu_uint16_t)-3 ); + __cpu_data_put_u16( P( 16, 0x16a ), 7 ); + I( &x, CPU_OP_ISMUL ); + x.operand[0] = R( 32, 0x16c ); + x.operand[1] = R( 16, 0x168 ); + x.operand[2] = R( 16, 0x16a ); + CHECK( __cpu_exec( &x ) == 0, "ISMUL" ); + CHECK( (__mpu_int32_t)__cpu_data_get_u32( P( 32, 0x16c ) ) == -21, "ISMUL result" ); + __cpu_data_put_u16( P( 16, 0x170 ), 100 ); + __cpu_data_put_u16( P( 16, 0x172 ), 7 ); + I( &x, CPU_OP_IDIV ); + x.operand[0] = R( 16, 0x174 ); + x.operand[1] = R( 16, 0x176 ); + x.operand[2] = R( 16, 0x170 ); + x.operand[3] = R( 16, 0x172 ); + CHECK( __cpu_exec( &x ) == 0, "IDIV" ); + CHECK( __cpu_data_get_u16( P( 16, 0x174 ) ) == 14 && __cpu_data_get_u16( P( 16, 0x176 ) ) == 2, "IDIV result" ); + { + mpu_int16_t qref, rref, nref, dref; + __cpu_data_put_u16( nref, (__mpu_uint16_t)-100 ); + __cpu_data_put_u16( dref, 7 ); + isdiv( ( mpu_int * ) qref, ( mpu_int * ) rref, ( mpu_int * ) nref, ( mpu_int * ) dref, 2 ); + memcpy( P( 16, 0x17a ), nref, 2 ); + memcpy( P( 16, 0x17c ), dref, 2 ); + I( &x, CPU_OP_ISDIV ); + x.operand[0] = R( 16, 0x17e ); + x.operand[1] = R( 16, 0x180 ); + x.operand[2] = R( 16, 0x17a ); + x.operand[3] = R( 16, 0x17c ); + CHECK( __cpu_exec( &x ) == 0, "ISDIV" ); + CHECK( memcmp( P( 16, 0x17e ), qref, 2 ) == 0 && memcmp( P( 16, 0x180 ), rref, 2 ) == 0, "ISDIV result" ); + } + *P( 8, 0x188 ) = 0x80; + I( &x, CPU_OP_IBITSLEN ); + x.operand[0] = R( 64, 0x190 ); + x.operand[1] = R( 8, 0x188 ); + CHECK( __cpu_exec( &x ) == 0, "IBITSLEN" ); + CHECK( get64( 0x190 ) == 8, "IBITSLEN result" ); + + /* Real conversion/utility coverage. */ + setreal( 64, 0x200, "-3.75" ); + I( &x, CPU_OP_RABS ); + x.operand[0] = R( 64, 0x208 ); + x.operand[1] = R( 64, 0x200 ); + CHECK( __cpu_exec( &x ) == 0, "RABS" ); + { + mpu_real64_t ref; + memcpy( ref, P( 64, 0x200 ), 8 ); + r_abs( ( mpu_real * ) ref, 8 ); + CHECK( memcmp( P( 64, 0x208 ), ref, 8 ) == 0, "RABS result" ); + } + I( &x, CPU_OP_RNEG ); + x.operand[0] = R( 64, 0x210 ); + x.operand[1] = R( 64, 0x200 ); + CHECK( __cpu_exec( &x ) == 0, "RNEG" ); + { + mpu_real64_t ref; + memcpy( ref, P( 64, 0x200 ), 8 ); + r_neg( ( mpu_real * ) ref, 8 ); + CHECK( memcmp( P( 64, 0x210 ), ref, 8 ) == 0, "RNEG result" ); + } + setreal( 64, 0x218, "2.0" ); + setreal( 64, 0x220, "3.0" ); + I( &x, CPU_OP_RCMP ); + x.operand[0] = R( 64, 0x228 ); + x.operand[1] = R( 64, 0x218 ); + x.operand[2] = R( 64, 0x220 ); + CHECK( __cpu_exec( &x ) == 0, "RCMP" ); + CHECK( geti64( 0x228 ) == -1, "RCMP result" ); + I( &x, CPU_OP_RCPY ); + x.operand[0] = R( 64, 0x230 ); + x.operand[1] = R( 64, 0x218 ); + CHECK( __cpu_exec( &x ) == 0, "RCPY" ); + CHECK( memcmp( P( 64, 0x230 ), P( 64, 0x218 ), 8 ) == 0, "RCPY result" ); + I( &x, CPU_OP_RCVT ); + x.operand[0] = R( 128, 0x240 ); + x.operand[1] = R( 64, 0x218 ); + CHECK( __cpu_exec( &x ) == 0, "RCVT" ); + { + mpu_real128_t ref; + r_cvt( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x218 ), 16, 8 ); + CHECK( memcmp( P( 128, 0x240 ), ref, 16 ) == 0, "RCVT result" ); + } + + /* floor/ceil/round, remain and the previously missing transcendental family. */ + { + struct U + { + cpu_opcode op; + void( *fn )( mpu_real *, mpu_real *, int ); + const char *n; + const char *src; + } + u[] = + { + {CPU_OP_RFLOOR,r_floor,"RFLOOR","3.75"},{CPU_OP_RCEIL,r_ceil,"RCEIL","3.25"},{CPU_OP_RROUND,r_round,"RROUND","3.60"}, + {CPU_OP_RLOG1P,r_log1p,"RLOG1P","0.25"},{CPU_OP_REXPM1,r_expm1,"REXPM1","0.25"}, + {CPU_OP_RASINH,r_asinh,"RASINH","0.25"},{CPU_OP_RACOSH,r_acosh,"RACOSH","2.0"},{CPU_OP_RATANH,r_atanh,"RATANH","0.25"} + }; + unsigned k; + for( k = 0; k < sizeof( u ) / sizeof( u[0] ); k++ ) + { + mpu_real64_t ref; + setreal( 64, 0x250, u[k].src ); + u[k].fn( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x250 ), 8 ); + I( &x, u[k].op ); + x.operand[0] = R( 64, 0x258 ); + x.operand[1] = R( 64, 0x250 ); + CHECK( __cpu_exec( &x ) == 0, u[k].n ); + CHECK( memcmp( P( 64, 0x258 ), ref, 8 ) == 0, u[k].n ); + } + } + setreal( 64, 0x260, "5.5" ); + setreal( 64, 0x268, "2.0" ); + { + mpu_real64_t ref; + r_remain( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x260 ), ( mpu_real * ) P( 64, 0x268 ), 8 ); + I( &x, CPU_OP_RREMAIN ); + x.operand[0] = R( 64, 0x270 ); + x.operand[1] = R( 64, 0x260 ); + x.operand[2] = R( 64, 0x268 ); + CHECK( __cpu_exec( &x ) == 0, "RREMAIN" ); + CHECK( memcmp( P( 64, 0x270 ), ref, 8 ) == 0, "RREMAIN result" ); + } + + /* frexp/ldexp/logb/modf and integer<->real conversions. */ + setreal( 64, 0x280, "6.5" ); + I( &x, CPU_OP_RFREXP ); + x.operand[0] = R( 64, 0x288 ); + x.operand[1] = R( 64, 0x290 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RFREXP" ); + { + mpu_real64_t rs; + mpu_int64_t re; + r_frexp( ( mpu_real * ) rs, ( mpu_int * ) re, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x288 ), rs, 8 ) == 0 && memcmp( P( 64, 0x290 ), re, 8 ) == 0, "RFREXP result" ); + } + I( &x, CPU_OP_RLDEXP ); + x.operand[0] = R( 64, 0x298 ); + x.operand[1] = R( 64, 0x288 ); + x.operand[2] = R( 64, 0x290 ); + CHECK( __cpu_exec( &x ) == 0, "RLDEXP" ); + { + mpu_real64_t ref; + r_ldexp( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x288 ), ( mpu_int * ) P( 64, 0x290 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x298 ), ref, 8 ) == 0, "RLDEXP result" ); + } + I( &x, CPU_OP_RLOGB ); + x.operand[0] = R( 64, 0x2a0 ); + x.operand[1] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RLOGB" ); + { + mpu_int64_t ri; + r_logb( ( mpu_int * ) ri, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2a0 ), ri, 8 ) == 0, "RLOGB result" ); + } + I( &x, CPU_OP_RMODF ); + x.operand[0] = R( 64, 0x2a8 ); + x.operand[1] = R( 64, 0x2b0 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RMODF" ); + { + mpu_real64_t rf, ri; + r_modf( ( mpu_real * ) rf, ( mpu_real * ) ri, ( mpu_real * ) P( 64, 0x280 ), 8 ); + CHECK( memcmp( P( 64, 0x2a8 ), rf, 8 ) == 0 && memcmp( P( 64, 0x2b0 ), ri, 8 ) == 0, "RMODF result" ); + } + put64( 0x2b8, 42 ); + I( &x, CPU_OP_ITOR ); + x.operand[0] = R( 64, 0x2c0 ); + x.operand[1] = R( 64, 0x2b8 ); + CHECK( __cpu_exec( &x ) == 0, "ITOR" ); + { + mpu_real64_t ref; + ltor( ( mpu_real * ) ref, ( mpu_int * ) P( 64, 0x2b8 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2c0 ), ref, 8 ) == 0, "ITOR result" ); + } + put64( 0x2c8, (__mpu_uint64_t)1844674407370955161UL ); + I( &x, CPU_OP_UITOR ); + x.operand[0] = R( 128, 0x2d0 ); + x.operand[1] = R( 64, 0x2c8 ); + CHECK( __cpu_exec( &x ) == 0, "UITOR" ); + { + mpu_real128_t ref; + ultor( ( mpu_real * ) ref, ( mpu_int * ) P( 64, 0x2c8 ), 16, 8 ); + CHECK( memcmp( P( 128, 0x2d0 ), ref, 16 ) == 0, "UITOR result" ); + } + I( &x, CPU_OP_RTOI ); + x.operand[0] = R( 64, 0x2e0 ); + x.operand[1] = R( 64, 0x2e8 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RTOI" ); + { + mpu_int64_t li; + mpu_real64_t fr; + rtol_frac( ( mpu_int * ) li, ( mpu_real * ) fr, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2e0 ), li, 8 ) == 0 && memcmp( P( 64, 0x2e8 ), fr, 8 ) == 0, "RTOI result" ); + } + setreal( 64, 0x2f0, "42.75" ); + I( &x, CPU_OP_RTOUI ); + x.operand[0] = R( 64, 0x2f8 ); + x.operand[1] = R( 64, 0x300 ); + x.operand[2] = R( 64, 0x2f0 ); + CHECK( __cpu_exec( &x ) == 0, "RTOUI" ); + { + mpu_int64_t li; + mpu_real64_t fr; + rtoul_frac( ( mpu_int * ) li, ( mpu_real * ) fr, ( mpu_real * ) P( 64, 0x2f0 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2f8 ), li, 8 ) == 0 && memcmp( P( 64, 0x300 ), fr, 8 ) == 0, "RTOUI result" ); + } + + /* Complete RCONST generator audit: every LibMPU value generator exposed by RCONST. */ + { + struct K + { + unsigned id; + void( *f )( mpu_real *, int ); + const char *n; + } + ks[] = + { + {CPU_RCONST_ZERO,_m_zero,"ZERO"},{CPU_RCONST_HALF,_m_half,"HALF"},{CPU_RCONST_ONE,_m_one,"ONE"}, + {CPU_RCONST_TWO,_m_two,"TWO"},{CPU_RCONST_TEN,_m_ten,"TEN"},{CPU_RCONST_MTEN,_m_mten,"MTEN"},{CPU_RCONST_32,_m_32,"32"}, + {CPU_RCONST_PI,_m_PI,"PI"},{CPU_RCONST_E,_m_E,"E"},{CPU_RCONST_1_LN2,_m_1_ln2,"1_LN2"},{CPU_RCONST_LN2,_m_ln2,"LN2"}, + {CPU_RCONST_1_LN10,_m_1_ln10,"1_LN10"},{CPU_RCONST_LN10,_m_ln10,"LN10"},{CPU_RCONST_1_LG2,_m_1_lg2,"1_LG2"},{CPU_RCONST_LG2,_m_lg2,"LG2"}, + {CPU_RCONST_PI_2,_m_PI_2,"PI_2"},{CPU_RCONST_PI_3,_m_PI_3,"PI_3"},{CPU_RCONST_PI_4,_m_PI_4,"PI_4"},{CPU_RCONST_PI_5,_m_PI_5,"PI_5"},{CPU_RCONST_PI_6,_m_PI_6,"PI_6"}, + {CPU_RCONST_1_PI,_m_1_PI,"1_PI"},{CPU_RCONST_2_PI,_m_2_PI,"2_PI"},{CPU_RCONST_3_PI,_m_3_PI,"3_PI"},{CPU_RCONST_4_PI,_m_4_PI,"4_PI"},{CPU_RCONST_5_PI,_m_5_PI,"5_PI"}, + {CPU_RCONST_2PI,_m_2PI,"2PI"},{CPU_RCONST_3PI,_m_3PI,"3PI"},{CPU_RCONST_1_2PI,_m_1_2PI,"1_2PI"},{CPU_RCONST_1_3PI,_m_1_3PI,"1_3PI"}, + {CPU_RCONST_1_4PI,_m_1_4PI,"1_4PI"},{CPU_RCONST_1_5PI,_m_1_5PI,"1_5PI"},{CPU_RCONST_1_6PI,_m_1_6PI,"1_6PI"},{CPU_RCONST_3PI_4,_m_3PI_4,"3PI_4"}, + {CPU_RCONST_SQRTPI,_m_SQRTPI,"SQRTPI"},{CPU_RCONST_1_SQRTPI,_m_1_SQRTPI,"1_SQRTPI"},{CPU_RCONST_2_SQRTPI,_m_2_SQRTPI,"2_SQRTPI"}, + {CPU_RCONST_SQRT2,_m_SQRT2,"SQRT2"},{CPU_RCONST_1_SQRT2,_m_1_SQRT2,"1_SQRT2"},{CPU_RCONST_SQRT3,_m_SQRT3,"SQRT3"},{CPU_RCONST_1_SQRT3,_m_1_SQRT3,"1_SQRT3"}, + {CPU_RCONST_DEGREE,_m_DEGREE,"DEGREE"},{CPU_RCONST_1_DEGREE,_m_1_DEGREE,"1_DEGREE"},{CPU_RCONST_GOLDENRATIO,_m_GOLDENRATIO,"GOLDENRATIO"}, + {CPU_RCONST_EULERGAMMA,_m_EULERGAMMA,"EULERGAMMA"},{CPU_RCONST_CATALAN,_m_CATALAN,"CATALAN"}, + {CPU_RCONST_EPSILON,_real_epsilon,"EPSILON"},{CPU_RCONST_IND,_ind,"IND"} + }; + unsigned k; + for( k = 0; k < sizeof( ks ) / sizeof( ks[0] ); k++ ) + { + mpu_real64_t ref; + ks[k].f( ( mpu_real * ) ref, 8 ); + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x420 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = (__mpu_uint8_t)ks[k].id; + CHECK( __cpu_exec( &x ) == 0, ks[k].n ); + CHECK( memcmp( P( 64, 0x420 ), ref, 8 ) == 0, ks[k].n ); + } + { + struct S + { + unsigned id; + void( *f )( mpu_real *, unsigned int, int ); + unsigned sign; + const char *n; + } + sp[] = + { + {CPU_RCONST_PNAN,_nan,0,"PNAN"},{CPU_RCONST_NNAN,_nan,1,"NNAN"}, + {CPU_RCONST_PNANMAX,_nan_max,0,"PNANMAX"},{CPU_RCONST_NNANMAX,_nan_max,1,"NNANMAX"}, + {CPU_RCONST_PNANMIN,_nan_min,0,"PNANMIN"},{CPU_RCONST_NNANMIN,_nan_min,1,"NNANMIN"}, + {CPU_RCONST_PINF,_inf,0,"PINF"},{CPU_RCONST_NINF,_inf,1,"NINF"}, + {CPU_RCONST_PREALMIN,_real_min,0,"PREALMIN"},{CPU_RCONST_NREALMIN,_real_min,1,"NREALMIN"}, + {CPU_RCONST_PREALMAX,_real_max,0,"PREALMAX"},{CPU_RCONST_NREALMAX,_real_max,1,"NREALMAX"}, + {CPU_RCONST_PZERO,_signull,0,"PZERO"},{CPU_RCONST_NZERO,_signull,1,"NZERO"} + }; + for( k = 0; k < sizeof( sp ) / sizeof( sp[0] ); k++ ) + { + mpu_real64_t ref; + sp[k].f( ( mpu_real * ) ref, sp[k].sign, 8 ); + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x428 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = (__mpu_uint8_t)sp[k].id; + CHECK( __cpu_exec( &x ) == 0, sp[k].n ); + CHECK( memcmp( P( 64, 0x428 ), ref, 8 ) == 0, sp[k].n ); + } + } + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x430 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = CPU_RCONST_NINF; + CHECK( __cpu_exec( &x ) == 0, "NINF predicate source" ); + I( &x, CPU_OP_RISINF ); + x.operand[0] = R( 64, 0x438 ); + x.operand[1] = R( 64, 0x430 ); + CHECK( __cpu_exec( &x ) == 0, "RISINF" ); + CHECK( geti64( 0x438 ) == 1, "RISINF result" ); + } + + /* Complex extraction, generation, conversion, conjugation and predicates. */ + setreal( 64, 0x500, "3.0" ); + setreal( 64, 0x508, "4.0" ); + I( &x, CPU_OP_CGEN ); + x.operand[0] = R( 128, 0x510 ); + x.operand[1] = R( 64, 0x500 ); + x.operand[2] = R( 64, 0x508 ); + CHECK( __cpu_exec( &x ) == 0, "CGEN" ); + { + mpu_complex64_t ref; + c_gen_complex( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x500 ), ( mpu_real * ) P( 64, 0x508 ), 8 ); + CHECK( memcmp( P( 128, 0x510 ), ref, 16 ) == 0, "CGEN result" ); + } + I( &x, CPU_OP_CREAL ); + x.operand[0] = R( 64, 0x520 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CREAL" ); + CHECK( memcmp( P( 64, 0x520 ), P( 64, 0x500 ), 8 ) == 0, "CREAL result" ); + I( &x, CPU_OP_CIMAG ); + x.operand[0] = R( 64, 0x528 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CIMAG" ); + CHECK( memcmp( P( 64, 0x528 ), P( 64, 0x508 ), 8 ) == 0, "CIMAG result" ); + I( &x, CPU_OP_CRTOC ); + x.operand[0] = R( 128, 0x530 ); + x.operand[1] = R( 64, 0x500 ); + CHECK( __cpu_exec( &x ) == 0, "CRTOC" ); + { + mpu_complex64_t ref; + c_real_to_complex( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x500 ), 8 ); + CHECK( memcmp( P( 128, 0x530 ), ref, 16 ) == 0, "CRTOC result" ); + } + I( &x, CPU_OP_CCONJ ); + x.operand[0] = R( 128, 0x540 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCONJ" ); + { + mpu_complex64_t ref; + c_conj( ( mpu_complex * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 128, 0x540 ), ref, 16 ) == 0, "CCONJ result" ); + } + I( &x, CPU_OP_CABS ); + x.operand[0] = R( 64, 0x550 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CABS" ); + { + mpu_real64_t ref; + c_abs( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x550 ), ref, 8 ) == 0, "CABS result" ); + } + I( &x, CPU_OP_CNORM ); + x.operand[0] = R( 64, 0x558 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CNORM" ); + { + mpu_real64_t ref; + c_norm( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x558 ), ref, 8 ) == 0, "CNORM result" ); + } + I( &x, CPU_OP_CARG ); + x.operand[0] = R( 64, 0x560 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CARG" ); + { + mpu_real64_t ref; + c_arg( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x560 ), ref, 8 ) == 0, "CARG result" ); + } + I( &x, CPU_OP_CISEQ ); + x.operand[0] = R( 64, 0x568 ); + x.operand[1] = R( 128, 0x510 ); + x.operand[2] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CISEQ" ); + CHECK( geti64( 0x568 ) == 1, "CISEQ result" ); + I( &x, CPU_OP_CISNE ); + x.operand[0] = R( 64, 0x570 ); + x.operand[1] = R( 128, 0x510 ); + x.operand[2] = R( 128, 0x540 ); + CHECK( __cpu_exec( &x ) == 0, "CISNE" ); + CHECK( geti64( 0x570 ) == 1, "CISNE result" ); + I( &x, CPU_OP_CCPY ); + x.operand[0] = R( 128, 0x580 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCPY" ); + CHECK( memcmp( P( 128, 0x580 ), P( 128, 0x510 ), 16 ) == 0, "CCPY result" ); + setreal( 64, 0x590, "5.0" ); + setreal( 64, 0x598, "0.5" ); + I( &x, CPU_OP_CPOLAR ); + x.operand[0] = R( 128, 0x5a0 ); + x.operand[1] = R( 64, 0x590 ); + x.operand[2] = R( 64, 0x598 ); + CHECK( __cpu_exec( &x ) == 0, "CPOLAR" ); + { + mpu_complex64_t ref; + c_polar( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x590 ), ( mpu_real * ) P( 64, 0x598 ), 8 ); + CHECK( memcmp( P( 128, 0x5a0 ), ref, 16 ) == 0, "CPOLAR result" ); + } + I( &x, CPU_OP_CCVT ); + x.operand[0] = R( 256, 0x5c0 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCVT" ); + { + mpu_complex128_t ref; + c_cvt( ( mpu_complex * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 16, 8 ); + CHECK( memcmp( P( 256, 0x5c0 ), ref, 32 ) == 0, "CCVT result" ); + } + + __cpu_free_context(); + if( failures ) + { + fprintf( stderr, "libcpu LibMPU coverage regression: %d failure(s)\n", failures ); + return( 1 ); + } + puts( "libcpu extended LibMPU instruction coverage: PASS" ); + return( 0 ); +} diff --git a/tests/tst-mpu-ops-runner.sh b/tests/tst-mpu-ops-runner.sh new file mode 100755 index 0000000..32bad1d --- /dev/null +++ b/tests/tst-mpu-ops-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-mpu-ops-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/mpu-ops.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00020 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-needed-runtime-runner.sh b/tests/tst-needed-runtime-runner.sh new file mode 100755 index 0000000..1119ff9 --- /dev/null +++ b/tests/tst-needed-runtime-runner.sh @@ -0,0 +1,193 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-needed-runtime-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern afoo +.text +_start_life: + LDI r64@0x00000, 40 + CALL afoo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global afoo +.extern bfoo +.text +afoo: + CALL bfoo + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global bfoo +.text +bfoo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" + +# A real recursive graph: main -> liba.so.1 -> libb.so.1. +"$CPU_LD" --shared --soname=libb.so.1 \ + -o "$TMP/lib/libb.so.1.0.0" "$TMP/b.o" +ln -s libb.so.1.0.0 "$TMP/lib/libb.so.1" +ln -s libb.so.1 "$TMP/lib/libb.so" + +"$CPU_LD" --shared --soname=liba.so.1 \ + -L"$TMP/lib" -lb -o "$TMP/lib/liba.so.1.0.0" "$TMP/a.o" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -o "$TMP/main" "$TMP/main.o" + +"$CPU_READELF" -d "$TMP/main" | grep -q 'NEEDED.*\[liba.so.1\]' +"$CPU_READELF" -d "$TMP/lib/liba.so.1.0.0" | \ + grep -q 'NEEDED.*\[libb.so.1\]' +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# A missing transitive dependency is reported by SONAME, before process load. +mv "$TMP/lib/libb.so.1" "$TMP/lib/libb.so.1.save" +if "$CPU_RUN" --library-path="$TMP/lib" "$TMP/main" \ + >"$TMP/missing.out" 2>"$TMP/missing.err"; then + echo "missing transitive dependency unexpectedly loaded" >&2 + exit 1 +fi +grep -q "cannot resolve DT_NEEDED 'libb.so.1'" "$TMP/missing.err" +mv "$TMP/lib/libb.so.1.save" "$TMP/lib/libb.so.1" + +# The resolved filename is not trusted: its internal SONAME must be the exact +# DT_NEEDED identity requested by the dependent object. +cat > "$TMP/wrong.s" <<'EOS' +.global afoo +.text +afoo: + RET +EOS +"$CPU_AS" -o "$TMP/wrong.o" "$TMP/wrong.s" +"$CPU_LD" --shared --soname=libwrong.so.9 \ + -o "$TMP/lib/libwrong.so.9.0.0" "$TMP/wrong.o" +rm "$TMP/lib/liba.so.1" +ln -s libwrong.so.9.0.0 "$TMP/lib/liba.so.1" +if "$CPU_RUN" --library-path="$TMP/lib" "$TMP/main" \ + >"$TMP/mismatch.out" 2>"$TMP/mismatch.err"; then + echo "SONAME mismatch unexpectedly loaded" >&2 + exit 1 +fi +grep -q "DT_NEEDED 'liba.so.1' resolved to SONAME 'libwrong.so.9'" \ + "$TMP/mismatch.err" +rm "$TMP/lib/liba.so.1" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" + +# Dependency cycles are legal graph edges. Registration by SONAME before +# recursive descent prevents A -> B -> A from recursing or loading twice. +cat > "$TMP/cycle-main.s" <<'EOS' +.global _life +.extern cycle_a +.text +_start_life: + LDI r64@0x00000, 40 + CALL cycle_a + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/cycle-a.s" <<'EOS' +.global cycle_a +.text +cycle_a: + LDI r64@0x00008, 4 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/cycle-b.s" <<'EOS' +.global cycle_b +.text +cycle_b: + RET +EOS +"$CPU_AS" -o "$TMP/cycle-main.o" "$TMP/cycle-main.s" +"$CPU_AS" -o "$TMP/cycle-a.o" "$TMP/cycle-a.s" +"$CPU_AS" -o "$TMP/cycle-b.o" "$TMP/cycle-b.s" + +# Bootstrap one side of the cycle, build B -> A, then rebuild A -> B. +"$CPU_LD" --shared --soname=libcyclea.so.1 \ + -o "$TMP/lib/libcyclea.so.1.0.0" "$TMP/cycle-a.o" +ln -s libcyclea.so.1.0.0 "$TMP/lib/libcyclea.so.1" +ln -s libcyclea.so.1 "$TMP/lib/libcyclea.so" +"$CPU_LD" --shared --soname=libcycleb.so.1 \ + -L"$TMP/lib" -lcyclea \ + -o "$TMP/lib/libcycleb.so.1.0.0" "$TMP/cycle-b.o" +ln -s libcycleb.so.1.0.0 "$TMP/lib/libcycleb.so.1" +ln -s libcycleb.so.1 "$TMP/lib/libcycleb.so" +"$CPU_LD" --shared --soname=libcyclea.so.1 \ + -L"$TMP/lib" -lcycleb \ + -o "$TMP/lib/libcyclea.so.1.0.0" "$TMP/cycle-a.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lcyclea -o "$TMP/cycle-main" "$TMP/cycle-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/cycle-main"`" = 44 ] + +# Explicit preload remains the override mechanism. An ABI-v3 preload with the +# same SONAME satisfies the normal dependency and keeps the established +# explicit-preload priority. +cat > "$TMP/direct-main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/foo.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/override.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/direct-main.o" "$TMP/direct-main.s" +"$CPU_AS" -o "$TMP/foo.o" "$TMP/foo.s" +"$CPU_AS" -o "$TMP/override.o" "$TMP/override.s" +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/lib/libfoo.so.1.0.0" "$TMP/foo.o" +ln -s libfoo.so.1.0.0 "$TMP/lib/libfoo.so.1" +ln -s libfoo.so.1 "$TMP/lib/libfoo.so" +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/override.so" "$TMP/override.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lfoo -o "$TMP/direct-main" "$TMP/direct-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/direct-main"`" = 42 ] +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload="$TMP/override.so" \ + --int-reg=r64@0x00018 "$TMP/direct-main"`" = 43 ] + +exit 0 diff --git a/tests/tst-nm-runner.sh b/tests/tst-nm-runner.sh new file mode 100755 index 0000000..573edbd --- /dev/null +++ b/tests/tst-nm-runner.sh @@ -0,0 +1,167 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-nm-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP" + +cat > "$TMP/symbols.s" <<'EOS' +.global zeta +.global data_global +.global bss_global +.extern missing +.weak weak_defined +.weak weak_missing + +.text +local_text: + RET +zeta: + RET +weak_defined: + RET + +.data +data_global: + .integer 64, 7 + +.bss +bss_global: + .zero 16 +EOS + +cat > "$TMP/main.s" <<'EOS' +.global _start +.global _life +.extern zeta +.text +_start_life: + CALL zeta + LDI r32@0x7ffc, 0 + RET +EOS + +cat > "$TMP/dyn.s" <<'EOS' +.global dyn_alpha +.global dyn_data +.weak dyn_weak +.text +dyn_alpha: + RET +dyn_weak: + RET +.data +dyn_data: + .integer 64, 1 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global archive_foo +.text +archive_foo: + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global archive_bar +.text +archive_bar: + RET +EOS + +"$CPU_AS" -o "$TMP/symbols.o" "$TMP/symbols.s" +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/dyn.o" "$TMP/dyn.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" + +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/program" "$TMP/main.o" "$TMP/symbols.o" +"$CPU_LD" --shared --soname=libnm.so.1 -o "$TMP/libnm.so.1.0.0" "$TMP/dyn.o" +"$CPU_AR" rcs "$TMP/libnm.a" "$TMP/a.o" "$TMP/b.o" + +"$CPU_NM" "$TMP/symbols.o" > "$TMP/default.txt" +grep -Eq ' T zeta$' "$TMP/default.txt" +grep -Eq ' t local_text$' "$TMP/default.txt" +grep -Eq ' D data_global$' "$TMP/default.txt" +grep -Eq ' B bss_global$' "$TMP/default.txt" +grep -Eq ' U missing$' "$TMP/default.txt" +grep -Eq ' W weak_defined$' "$TMP/default.txt" +grep -Eq ' w weak_missing$' "$TMP/default.txt" + +# Default order is lexical by symbol name. +awk '{print $NF}' "$TMP/default.txt" > "$TMP/names.txt" +LC_ALL=C sort "$TMP/names.txt" > "$TMP/names.sorted" +cmp "$TMP/names.txt" "$TMP/names.sorted" + +"$CPU_NM" -g "$TMP/symbols.o" > "$TMP/extern.txt" +if grep -q 'local_text$' "$TMP/extern.txt"; then + echo "mcpu-nm -g unexpectedly displayed a local symbol" >&2 + exit 1 +fi +grep -q 'zeta$' "$TMP/extern.txt" + +"$CPU_NM" -u "$TMP/symbols.o" > "$TMP/undef.txt" +grep -Eq ' U missing$' "$TMP/undef.txt" +grep -Eq ' w weak_missing$' "$TMP/undef.txt" +if grep -q 'zeta$' "$TMP/undef.txt"; then + echo "mcpu-nm -u unexpectedly displayed a defined symbol" >&2 + exit 1 +fi + +"$CPU_NM" -n "$TMP/symbols.o" > "$TMP/numeric.txt" +# The first two defined text symbols are ordered by value, not alphabetically. +line_local=`grep -n ' local_text$' "$TMP/numeric.txt" | cut -d: -f1` +line_zeta=`grep -n ' zeta$' "$TMP/numeric.txt" | cut -d: -f1` +test "$line_local" -lt "$line_zeta" + +"$CPU_NM" -r "$TMP/symbols.o" > "$TMP/reverse.txt" +first_name=`awk 'NF { print $NF; exit }' "$TMP/reverse.txt"` +test "$first_name" = zeta + +"$CPU_NM" -p "$TMP/symbols.o" > "$TMP/nosort.txt" +test -s "$TMP/nosort.txt" +"$CPU_NM" -a "$TMP/symbols.o" > "$TMP/all.txt" +test -s "$TMP/all.txt" + +"$CPU_NM" "$TMP/program" > "$TMP/program.txt" +# ABI-v1 ET_EXEC has no retained .symtab; successful inspection is the contract. +test ! -s "$TMP/program.txt" + +"$CPU_NM" -D "$TMP/libnm.so.1.0.0" > "$TMP/dynamic.txt" +grep -q ' dyn_alpha$' "$TMP/dynamic.txt" +grep -q ' dyn_data$' "$TMP/dynamic.txt" +grep -Eq ' W dyn_weak$' "$TMP/dynamic.txt" + +"$CPU_NM" "$TMP/libnm.a" > "$TMP/archive.txt" +grep -F "$TMP/libnm.a:a.o:" "$TMP/archive.txt" >/dev/null +grep -F "$TMP/libnm.a:b.o:" "$TMP/archive.txt" >/dev/null +grep -q ' archive_foo$' "$TMP/archive.txt" +grep -q ' archive_bar$' "$TMP/archive.txt" + +"$CPU_NM" -A "$TMP/libnm.a" > "$TMP/archive-prefix.txt" +grep -F "$TMP/libnm.a:a.o: " "$TMP/archive-prefix.txt" | grep -q ' archive_foo$' +grep -F "$TMP/libnm.a:b.o: " "$TMP/archive-prefix.txt" | grep -q ' archive_bar$' +if grep -Fx "$TMP/libnm.a:a.o:" "$TMP/archive-prefix.txt" >/dev/null; then + echo "mcpu-nm -A unexpectedly emitted archive member headings" >&2 + exit 1 +fi + +"$CPU_NM" "$TMP/a.o" "$TMP/b.o" > "$TMP/multi.txt" +grep -Fx "$TMP/a.o:" "$TMP/multi.txt" >/dev/null +grep -Fx "$TMP/b.o:" "$TMP/multi.txt" >/dev/null + +"$CPU_NM" -A "$TMP/a.o" > "$TMP/file-prefix.txt" +grep -F "$TMP/a.o: " "$TMP/file-prefix.txt" | grep -q ' archive_foo$' + +dd if=/dev/zero of="$TMP/bad" bs=256 count=1 2>/dev/null +if "$CPU_NM" "$TMP/bad" > /dev/null 2> "$TMP/bad.err"; then + echo "mcpu-nm unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" + +"$CPU_NM" --help | grep -q -- '--dynamic' +"$CPU_NM" --help | grep -q -- '--print-file-name' +"$CPU_NM" --version | grep -Fq "$PACKAGE_VERSION" diff --git a/tests/tst-objcopy-runner.sh b/tests/tst-objcopy-runner.sh new file mode 100755 index 0000000..96b501b --- /dev/null +++ b/tests/tst-objcopy-runner.sh @@ -0,0 +1,160 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${CPU_AR:?}" "${CPU_NM:?}" "${CPU_OBJCOPY:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-objcopy-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.text +_start_life: + LDI r64@0x00000, 42 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/add-one.s" <<'EOS' +.global add_one +.text +add_one: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/add-one.o" "$TMP/add-one.s" +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_AR" rcs "$TMP/libadd.a" "$TMP/add-one.o" + +# Plain MELF -> MELF copying is byte-for-byte when no edit is requested. +"$CPU_OBJCOPY" "$TMP/main" "$TMP/main.copy" +cmp "$TMP/main" "$TMP/main.copy" + +printf 'metadata-one\n' > "$TMP/note-one" +printf 'metadata-two\n' > "$TMP/note-two" + +# Add non-ALLOC metadata without touching the executable image. +"$CPU_OBJCOPY" --add-section=.note.test="$TMP/note-one" \ + "$TMP/main" "$TMP/main.note" +"$CPU_READELF" -S "$TMP/main.note" | grep -q '\.note\.test' +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.note")" = 42 + +# Dump is read-only and may inspect runtime sections as well as metadata. +"$CPU_OBJCOPY" --dump-section=.note.test="$TMP/note.dump" \ + "$TMP/main.note" "$TMP/main.dump-copy" +cmp "$TMP/note-one" "$TMP/note.dump" +"$CPU_OBJCOPY" --dump-section=.text="$TMP/text.before" \ + "$TMP/main" "$TMP/main.text-copy" +"$CPU_OBJCOPY" --dump-section=.text="$TMP/text.after" \ + "$TMP/main.note" "$TMP/main.note-copy" +cmp "$TMP/text.before" "$TMP/text.after" + +# Update and rename apply only to safe non-ALLOC PROGBITS metadata. +"$CPU_OBJCOPY" --update-section=.note.test="$TMP/note-two" \ + "$TMP/main.note" "$TMP/main.updated" +"$CPU_OBJCOPY" --dump-section=.note.test="$TMP/updated.dump" \ + "$TMP/main.updated" "$TMP/main.updated-copy" +cmp "$TMP/note-two" "$TMP/updated.dump" + +"$CPU_OBJCOPY" --rename-section=.note.test=.note.renamed \ + "$TMP/main.updated" "$TMP/main.renamed" +"$CPU_READELF" -S "$TMP/main.renamed" | grep -q '\.note\.renamed' +if "$CPU_READELF" -S "$TMP/main.renamed" | grep -q '\.note\.test'; then + echo "old section name survived mcpu-objcopy rename" >&2 + exit 1 +fi + +# Safe metadata can be removed; runtime behavior stays unchanged. +"$CPU_OBJCOPY" -R .note.renamed "$TMP/main.renamed" "$TMP/main.removed" +if "$CPU_READELF" -S "$TMP/main.removed" | grep -q '\.note\.renamed'; then + echo "removed metadata section survived mcpu-objcopy" >&2 + exit 1 +fi +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.removed")" = 42 + +# Runtime-visible and structural sections are protected. +if "$CPU_OBJCOPY" -R .text "$TMP/main" "$TMP/bad-remove" >/dev/null 2>&1; then + echo "mcpu-objcopy removed .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --update-section=.text="$TMP/note-one" \ + "$TMP/main" "$TMP/bad-update" >/dev/null 2>&1; then + echo "mcpu-objcopy updated .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --rename-section=.text=.foo \ + "$TMP/main" "$TMP/bad-rename" >/dev/null 2>&1; then + echo "mcpu-objcopy renamed .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --add-section=.text="$TMP/note-one" \ + "$TMP/main" "$TMP/bad-duplicate" >/dev/null 2>&1; then + echo "mcpu-objcopy added duplicate .text" >&2 + exit 1 +fi + +# --only-section is strict: an executable cannot lose its runtime image. +if "$CPU_OBJCOPY" -j .note.test "$TMP/main.note" "$TMP/bad-only" \ + >/dev/null 2>&1; then + echo "mcpu-objcopy created an incomplete ET_EXEC with -j" >&2 + exit 1 +fi + +# ET_REL link semantics are protected, but optional metadata may be filtered. +"$CPU_OBJCOPY" --add-section=.note.rel="$TMP/note-one" \ + "$TMP/main.o" "$TMP/main.note.o" +"$CPU_OBJCOPY" \ + -j .text -j .symtab -j .strtab -j .mcpu.target -j .shstrtab \ + "$TMP/main.note.o" "$TMP/main.filtered.o" +if "$CPU_READELF" -S "$TMP/main.filtered.o" | grep -q '\.note\.rel'; then + echo "--only-section failed to remove optional ET_REL metadata" >&2 + exit 1 +fi +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main.filtered" \ + "$TMP/main.filtered.o" +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.filtered")" = 42 + +if "$CPU_OBJCOPY" -R .symtab "$TMP/main.o" "$TMP/bad-rel" \ + >/dev/null 2>&1; then + echo "mcpu-objcopy removed ET_REL .symtab" >&2 + exit 1 +fi + +# Archives are transformed member-by-member and retain their symbol index. +"$CPU_OBJCOPY" --add-section=.note.arc="$TMP/note-one" \ + "$TMP/libadd.a" "$TMP/libadd.note.a" +"$CPU_NM" -A "$TMP/libadd.note.a" | grep -q 'add_one' +mkdir "$TMP/extract" +( + cd "$TMP/extract" + "$CPU_AR" x ../libadd.note.a + "$CPU_READELF" -S add-one.o | grep -q '\.note\.arc' +) +"$CPU_OBJCOPY" -R .note.arc "$TMP/libadd.note.a" "$TMP/libadd.clean.a" +"$CPU_NM" -A "$TMP/libadd.clean.a" | grep -q 'add_one' +if "$CPU_OBJCOPY" --dump-section=.text="$TMP/archive.dump" \ + "$TMP/libadd.a" "$TMP/libadd.dump.a" >/dev/null 2>&1; then + echo "mcpu-objcopy accepted ambiguous archive --dump-section" >&2 + exit 1 +fi + +"$CPU_OBJCOPY" --help | grep -q -- '--add-section=NAME=FILE' +"$CPU_OBJCOPY" --version | grep -Fq "$PACKAGE_VERSION" + +if "$CPU_OBJCOPY" "$TMP/main" >/dev/null 2>&1; then + echo "mcpu-objcopy unexpectedly accepted a missing output file" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" "$TMP/main" "$TMP/main" >/dev/null 2>&1; then + echo "mcpu-objcopy unexpectedly accepted in-place operation" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-objdump-runner.sh b/tests/tst-objdump-runner.sh new file mode 100755 index 0000000..94142eb --- /dev/null +++ b/tests/tst-objdump-runner.sh @@ -0,0 +1,157 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_AR:?}" "${CPU_OBJDUMP:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-objdump-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP" + +cat > "$TMP/unit.s" <<'EOS' +.global entry +.global data_global +.extern external_function +.text +entry: + SETF CF + CLRF AF + CLRCPUF + CLRMPUF + LDI r64@0x00000, 41 + CALL local_helper + CALL external_function + RET +local_helper: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +.data +data_global: + .integer 64, 7 +data_pointer: + .ptr data_global +EOS + +cat > "$TMP/dyn.s" <<'EOS' +.global dyn_api +.global dyn_data +.text +dyn_api: + CALL local_dyn + RET +local_dyn: + LDI r64@0x00008, 2 + RET +.data +dyn_data: + .integer 64, 9 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global archive_a +.text +archive_a: + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global archive_b +.text +archive_b: + RET +EOS + +"$CPU_AS" -o "$TMP/unit.o" "$TMP/unit.s" +"$CPU_AS" -o "$TMP/dyn.o" "$TMP/dyn.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --shared --soname=libobjdump.so.1 -o "$TMP/libobjdump.so" "$TMP/dyn.o" +"$CPU_AR" rcs "$TMP/libobjdump.a" "$TMP/a.o" "$TMP/b.o" + +"$CPU_OBJDUMP" -h "$TMP/unit.o" > "$TMP/headers.txt" +grep -q '^Sections:$' "$TMP/headers.txt" +grep -q '\.text' "$TMP/headers.txt" +grep -q '\.data' "$TMP/headers.txt" +grep -q 'PROGBITS.*CODE' "$TMP/headers.txt" + +"$CPU_OBJDUMP" -t "$TMP/unit.o" > "$TMP/symbols.txt" +grep -q '^SYMBOL TABLE:$' "$TMP/symbols.txt" +grep -q ' entry$' "$TMP/symbols.txt" +grep -q ' local_helper$' "$TMP/symbols.txt" +grep -q ' data_global$' "$TMP/symbols.txt" +grep -q ' external_function$' "$TMP/symbols.txt" + +"$CPU_OBJDUMP" -r "$TMP/unit.o" > "$TMP/relocs.txt" +grep -q 'RELOCATION RECORDS FOR \[.text\]' "$TMP/relocs.txt" +grep -q 'R_MCPU_CODE64.*local_helper' "$TMP/relocs.txt" +grep -q 'R_MCPU_CODE64.*external_function' "$TMP/relocs.txt" + +"$CPU_OBJDUMP" -d -r "$TMP/unit.o" > "$TMP/disasm.txt" +grep -q 'Disassembly of section .text:' "$TMP/disasm.txt" +grep -q '<entry>:' "$TMP/disasm.txt" +grep -q '<local_helper>:' "$TMP/disasm.txt" +grep -q 'SETF.*CF' "$TMP/disasm.txt" +grep -q 'CLRF.*AF' "$TMP/disasm.txt" +grep -q 'CLRCPUF' "$TMP/disasm.txt" +grep -q 'CLRMPUF' "$TMP/disasm.txt" +grep -q 'LDI.*r64@0x00000, 41' "$TMP/disasm.txt" +grep -q 'CALL.*<local_helper>' "$TMP/disasm.txt" +grep -q 'CALL.*<external_function>' "$TMP/disasm.txt" +grep -q 'R_MCPU_CODE64.*local_helper' "$TMP/disasm.txt" +# -r remains complete with -d: non-executable-section relocations are +# printed separately while .text relocations stay next to instructions. +grep -q 'RELOCATION RECORDS FOR \[.data\]' "$TMP/disasm.txt" +grep -q 'R_MCPU_PTR64_DATA.*data_global' "$TMP/disasm.txt" + +"$CPU_OBJDUMP" -s -j .data "$TMP/unit.o" > "$TMP/data.txt" +grep -q 'Contents of section .data:' "$TMP/data.txt" +if grep -q 'Contents of section .text:' "$TMP/data.txt"; then + echo "mcpu-objdump -j .data unexpectedly displayed .text contents" >&2 + exit 1 +fi + +"$CPU_OBJDUMP" -D -j .data "$TMP/unit.o" > "$TMP/all-data.txt" +grep -q 'Disassembly of section .data:' "$TMP/all-data.txt" + +"$CPU_OBJDUMP" -T "$TMP/libobjdump.so" > "$TMP/dynamic.txt" +grep -q '^DYNAMIC SYMBOL TABLE:$' "$TMP/dynamic.txt" +grep -q ' dyn_api$' "$TMP/dynamic.txt" +grep -q ' dyn_data$' "$TMP/dynamic.txt" + +"$CPU_OBJDUMP" -d -r "$TMP/libobjdump.so" > "$TMP/dynamic-disasm.txt" +grep -q '<dyn_api>:' "$TMP/dynamic-disasm.txt" +grep -q 'CALL.*<.L\$0\$0>' "$TMP/dynamic-disasm.txt" +grep -q 'R_MCPU_CODE64.*.L\$0\$0' "$TMP/dynamic-disasm.txt" + +"$CPU_OBJDUMP" -t "$TMP/libobjdump.a" > "$TMP/archive.txt" +grep -F "$TMP/libobjdump.a(a.o):" "$TMP/archive.txt" >/dev/null +grep -F "$TMP/libobjdump.a(b.o):" "$TMP/archive.txt" >/dev/null +grep -q ' archive_a$' "$TMP/archive.txt" +grep -q ' archive_b$' "$TMP/archive.txt" + +# Grouped short options and attached -j argument are accepted. +"$CPU_OBJDUMP" -hdr -j.text "$TMP/unit.o" > "$TMP/grouped.txt" +grep -q 'Sections:' "$TMP/grouped.txt" +grep -q 'Disassembly of section .text:' "$TMP/grouped.txt" +grep -q 'R_MCPU_CODE64' "$TMP/grouped.txt" + +# Missing/invalid input remains a hard error. +dd if=/dev/zero of="$TMP/bad" bs=256 count=1 2>/dev/null +if "$CPU_OBJDUMP" -h "$TMP/bad" > /dev/null 2> "$TMP/bad.err"; then + echo "mcpu-objdump unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" + +if "$CPU_OBJDUMP" "$TMP/unit.o" > /dev/null 2> "$TMP/no-option.err"; then + echo "mcpu-objdump unexpectedly accepted no display option" >&2 + exit 1 +fi +grep -q 'at least one display option is required' "$TMP/no-option.err" + +"$CPU_OBJDUMP" --help | grep -q -- '--disassemble-all' +"$CPU_OBJDUMP" --help | grep -q -- '--dynamic-syms' +"$CPU_OBJDUMP" --help | grep -q -- '--section=NAME' +"$CPU_OBJDUMP" --version | grep -Fq "$PACKAGE_VERSION" diff --git a/tests/tst-overlap.c b/tests/tst-overlap.c new file mode 100644 index 0000000..9de9286 --- /dev/null +++ b/tests/tst-overlap.c @@ -0,0 +1,104 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +int +main( void ) +{ + __mpu_UNIT_t *r64; + __mpu_UNIT_t *r32_0; + __mpu_UNIT_t *r32_4; + __mpu_UNIT_t *r16_2; + __mpu_UNIT_t *r8_3; + unsigned int i; + + __cpu_init(); + + r64 = __cpu_reg_ptr( 64, 0 ); + r32_0 = __cpu_reg_ptr( 32, 0 ); + r32_4 = __cpu_reg_ptr( 32, 4 ); + r16_2 = __cpu_reg_ptr( 16, 2 ); + r8_3 = __cpu_reg_ptr( 8, 3 ); + + CHECK( r64 != 0, "r64@0 pointer" ); + CHECK( r32_0 != 0, "r32@0 pointer" ); + CHECK( r32_4 != 0, "r32@4 pointer" ); + CHECK( r16_2 != 0, "r16@2 pointer" ); + CHECK( r8_3 != 0, "r8@3 pointer" ); + + CHECK( r64 == r32_0, "r64@0 and r32@0 start at same byte" ); + CHECK( r32_4 == r64 + 4, "r32@4 overlaps upper four bytes of r64@0" ); + CHECK( r16_2 == r64 + 2, "r16@2 overlaps bytes 2..3 of r64@0" ); + CHECK( r8_3 == r64 + 3, "r8@3 overlaps byte 3 of r64@0" ); + + for( i = 0; i < 8; ++i ) r64[i] = (__mpu_UNIT_t)( 0x10 + i ); + + CHECK( r32_0[0] == 0x10 && r32_0[1] == 0x11 && + r32_0[2] == 0x12 && r32_0[3] == 0x13, + "r32@0 sees bytes written through r64@0" ); + + CHECK( r32_4[0] == 0x14 && r32_4[1] == 0x15 && + r32_4[2] == 0x16 && r32_4[3] == 0x17, + "r32@4 sees bytes written through r64@0" ); + + CHECK( r16_2[0] == 0x12 && r16_2[1] == 0x13, + "r16@2 sees bytes written through r64@0" ); + + *r8_3 = 0xa5; + CHECK( r64[3] == 0xa5, + "write through r8@3 changes r64@0 storage" ); + CHECK( r32_0[3] == 0xa5, + "write through r8@3 changes r32@0 storage" ); + CHECK( r16_2[1] == 0xa5, + "write through r8@3 changes r16@2 storage" ); + + /* Check the same property at a non-zero naturally aligned address. */ + { + __mpu_UNIT_t *r128 = __cpu_reg_ptr( 128, 0x00100 ); + __mpu_UNIT_t *r32 = __cpu_reg_ptr( 32, 0x0010c ); + __mpu_UNIT_t *r8 = __cpu_reg_ptr( 8, 0x0010f ); + + CHECK( r128 != 0 && r32 != 0 && r8 != 0, + "overlap pointers at 0x00100" ); + CHECK( r32 == r128 + 12, + "r32@0x0010c inside r128@0x00100" ); + CHECK( r8 == r128 + 15, + "r8@0x0010f inside r128@0x00100" ); + + r128[15] = 0x5a; + CHECK( *r8 == 0x5a, + "r8 view sees r128 storage" ); + + r32[3] = 0xc3; + CHECK( r128[15] == 0xc3, + "r32 view changes r128 storage" ); + } + + if( failures != 0 ) + { + fprintf( stderr, "libcpu overlap regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 overlapping register views regression: PASS\n" ); + printf( "overlap is byte-storage aliasing and is endian independent\n" ); + + return( 0 ); +} diff --git a/tests/tst-partial-link-runner.sh b/tests/tst-partial-link-runner.sh new file mode 100755 index 0000000..2f08e16 --- /dev/null +++ b/tests/tst-partial-link-runner.sh @@ -0,0 +1,276 @@ +#!/bin/bash +set -e + +CPU_AS=${CPU_AS:-../utils/mcpu-as} +CPU_LD=${CPU_LD:-../utils/mcpu-ld} +CPU_RUN=${CPU_RUN:-../utils/mcpu-run} +CPU_READELF=${CPU_READELF:-../utils/mcpu-readelf} +CPU_AR=${CPU_AR:-../utils/mcpu-ar} + +TMP=${TMPDIR:-/tmp}/libmcpu-partial-link-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP/lib" + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern add_two +.extern shared_value +.text +_start_life: + LDI r64@0x00000, 40 + CALL add_two + STORE shared_value, r64@0x00000 + LOAD r64@0x00018, shared_value + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/lib.s" <<'EOS' +.global add_two +.global shared_value +.global shared_ptr +.global add_two_ptr +.data +.align 32 +shared_value: + .integer 64, 0 +shared_ptr: + .ptr shared_value +add_two_ptr: + .cptr add_two +.text +add_two: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/lib.o" "$TMP/lib.s" + +# A complete partial link is still ET_REL and may be used as the sole input +# object of a later final link. +"$CPU_LD" -r -o "$TMP/combined.o" "$TMP/main.o" "$TMP/lib.o" +"$CPU_READELF" -h "$TMP/combined.o" > "$TMP/combined.header" +"$CPU_READELF" -s "$TMP/combined.o" > "$TMP/combined.symbols" +"$CPU_READELF" -r "$TMP/combined.o" > "$TMP/combined.relocs" +grep -q 'Type:.*REL (Relocatable file)' "$TMP/combined.header" +grep -q 'add_two' "$TMP/combined.symbols" +grep -q 'shared_value' "$TMP/combined.symbols" +grep -q 'add_two' "$TMP/combined.relocs" +grep -q 'shared_value' "$TMP/combined.relocs" + +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/combined-app" "$TMP/combined.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/combined-app"`" = 42 ] + +# -r does not require lifecycle completeness and preserves unresolved symbols. +"$CPU_LD" -r -o "$TMP/main-partial.o" "$TMP/main.o" +"$CPU_READELF" -s "$TMP/main-partial.o" | grep -q 'UND.*add_two' +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/late-app" "$TMP/main-partial.o" "$TMP/lib.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/late-app"`" = 42 ] + +"$CPU_LD" -r -o "$TMP/library-only.o" "$TMP/lib.o" +"$CPU_READELF" -h "$TMP/library-only.o" | grep -q 'REL (Relocatable file)' + +# LOCAL symbols from separate objects remain private even when they have the +# same spelling. Their relocations must still refer to their own definitions. +cat > "$TMP/local-a.s" <<'EOS' +.global fa +.text +fa: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/local-b.s" <<'EOS' +.global fb +.text +fb: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/local-main.s" <<'EOS' +.global _life +.extern fa +.extern fb +.text +_start_life: + LDI r64@0x00000, 39 + CALL fa + CALL fb + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/local-a.o" "$TMP/local-a.s" +"$CPU_AS" -o "$TMP/local-b.o" "$TMP/local-b.s" +"$CPU_AS" -o "$TMP/local-main.o" "$TMP/local-main.s" +"$CPU_LD" -r -o "$TMP/local-combined.o" "$TMP/local-a.o" "$TMP/local-b.o" +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/local-app" "$TMP/local-main.o" "$TMP/local-combined.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/local-app"`" = 42 ] + +# A strong GLOBAL definition replaces a WEAK definition in partial output. +cat > "$TMP/weak.s" <<'EOS' +.weak pick +.text +pick: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/strong.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/pick-main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/weak.o" "$TMP/weak.s" +"$CPU_AS" -o "$TMP/strong.o" "$TMP/strong.s" +"$CPU_AS" -o "$TMP/pick-main.o" "$TMP/pick-main.s" +"$CPU_LD" -r -o "$TMP/pick.o" "$TMP/weak.o" "$TMP/strong.o" +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/pick-app" "$TMP/pick-main.o" "$TMP/pick.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-app"`" = 42 ] + +# Two strong definitions are already an error at partial-link time. +cat > "$TMP/clash-a.s" <<'EOS' +.global clash +.text +clash: + RET +EOS +cat > "$TMP/clash-b.s" <<'EOS' +.global clash +.text +clash: + RET +EOS +"$CPU_AS" -o "$TMP/clash-a.o" "$TMP/clash-a.s" +"$CPU_AS" -o "$TMP/clash-b.o" "$TMP/clash-b.s" +if "$CPU_LD" -r -o "$TMP/clash.o" "$TMP/clash-a.o" "$TMP/clash-b.o" \ + > /dev/null 2> "$TMP/clash.err"; then + echo "partial link unexpectedly accepted duplicate GLOBAL definitions" >&2 + exit 1 +fi +grep -q 'multiple definition of global symbol: clash' "$TMP/clash.err" + +# -r may extract required ET_REL members from libNAME.a, but never consumes a +# same-named shared object. The unused archive member must stay outside the +# partial object even though it contains an unresolved reference of its own. +cat > "$TMP/usepart.s" <<'EOS' +.global call_part +.extern part +.text +call_part: + CALL part + RET +EOS +cat > "$TMP/part-static.s" <<'EOS' +.global part +.text +part: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/part-shared.s" <<'EOS' +.global part +.text +part: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/unused.s" <<'EOS' +.global unused_archive_member +.extern missing_from_unused_member +.text +unused_archive_member: + CALL missing_from_unused_member + RET +EOS +cat > "$TMP/archive-main.s" <<'EOS' +.global _life +.extern call_part +.text +_start_life: + LDI r64@0x00000, 40 + CALL call_part + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/usepart.o" "$TMP/usepart.s" +"$CPU_AS" -o "$TMP/part-static.o" "$TMP/part-static.s" +"$CPU_AS" -o "$TMP/part-shared.o" "$TMP/part-shared.s" +"$CPU_AS" -o "$TMP/unused.o" "$TMP/unused.s" +"$CPU_AS" -o "$TMP/archive-main.o" "$TMP/archive-main.s" +"$CPU_AR" rcs "$TMP/lib/libpart.a" "$TMP/part-static.o" "$TMP/unused.o" +"$CPU_LD" --shared --soname=libpart.so.1 \ + -o "$TMP/lib/libpart.so.1.0.0" "$TMP/part-shared.o" +ln -s libpart.so.1.0.0 "$TMP/lib/libpart.so.1" +ln -s libpart.so.1 "$TMP/lib/libpart.so" + +"$CPU_LD" -r -L"$TMP/lib" -lpart -o "$TMP/from-archive.o" "$TMP/usepart.o" +"$CPU_READELF" -s "$TMP/from-archive.o" > "$TMP/from-archive.symbols" +grep -q ' part$' "$TMP/from-archive.symbols" +if grep -q 'unused_archive_member' "$TMP/from-archive.symbols"; then + echo "partial link unexpectedly extracted an unused archive member" >&2 + exit 1 +fi +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/archive-app" "$TMP/archive-main.o" "$TMP/from-archive.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/archive-app"`" = 42 ] + +# The resulting partial object is an ordinary ET_REL and can itself be stored +# in a static archive. +"$CPU_AR" rcs "$TMP/libcombined.a" "$TMP/combined.o" +[ "`"$CPU_AR" t "$TMP/libcombined.a"`" = combined.o ] + +# Options that describe final executable/shared-image state are invalid in -r. +if "$CPU_LD" -r --dynamic -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --dynamic unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --shared --soname=bad.so -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --shared unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --stack-size=4096 -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --stack-size unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --no-heap -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --no-heap unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --soname=bad.so -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --soname unexpectedly accepted" >&2 + exit 1 +fi + +"$CPU_LD" --help 2>&1 | grep -q -- '-r.*partial-link MELF ET_REL' + +exit 0 diff --git a/tests/tst-pointer-arithmetic.c b/tests/tst-pointer-arithmetic.c new file mode 100644 index 0000000..76414fa --- /dev/null +++ b/tests/tst-pointer-arithmetic.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".data\n" +".align 8\n" +"values:\n" +" .integer 64, 11\n" +" .integer 64, 22\n" +" .integer 64, 33\n" +".text\n" +" LEA r64@0x00000, values\n" +" PADDI r64@0x00008, r64@0x00000, 16\n" +" LOADP r64@0x00010, r64@0x00008\n" +" PDIFF r64@0x00018, r64@0x00008, r64@0x00000\n" +" PSUBI r64@0x00020, r64@0x00008, 8\n" +" LOADP r64@0x00028, r64@0x00020\n" +" LDI r64@0x00030, 8\n" +" PADD r64@0x00038, r64@0x00000, r64@0x00030\n" +" LOADP r64@0x00040, r64@0x00038\n" +" PSUB r64@0x00048, r64@0x00038, r64@0x00030\n" +" LOADP r64@0x00050, r64@0x00048\n" +" PADDI r64@0x00058, r64@0x00008, -16\n" +" LOADP r64@0x00060, r64@0x00058\n" +" PSUBI r64@0x00068, r64@0x00000, -16\n" +" LOADP r64@0x00070, r64@0x00068\n" +" HALT\n"; + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static __mpu_int64_t +reg_i64( unsigned address ) +{ + __mpu_int64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +set_reg_i64( unsigned address, __mpu_int64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p, saved, minbits; + __mpu_int64_t mindelta; + __mpu_uint32_t psw; + char err[256]; + + __cpu_init(); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "pointer arithmetic program run" ); + + CHECK( __cpu_ptr_segment( reg_u64( 0x00008 ) ) == CPU_PTR_SEG_DATA, "PADDI preserves DATA segment" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00008 ) ) == 32, "PADDI offset" ); + CHECK( reg_i64( 0x00010 ) == 33, "PADDI + LOADP" ); + CHECK( reg_i64( 0x00018 ) == 16, "PDIFF byte difference" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00020 ) ) == 24, "PSUBI offset" ); + CHECK( reg_i64( 0x00028 ) == 22, "PSUBI + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00038 ) ) == 24, "PADD register displacement" ); + CHECK( reg_i64( 0x00040 ) == 22, "PADD + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00048 ) ) == 16, "PSUB register displacement" ); + CHECK( reg_i64( 0x00050 ) == 11, "PSUB + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00058 ) ) == 16, "PADDI negative displacement" ); + CHECK( reg_i64( 0x00060 ) == 11, "PADDI negative + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00068 ) ) == 32, "PSUBI negative displacement" ); + CHECK( reg_i64( 0x00070 ) == 33, "PSUBI negative + LOADP" ); + + /* Arithmetic is defined on any structurally valid PTR64 segment selector. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 100 ) == 0, "make reserved-segment PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, 23 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PADD; + regop( &i.operand[0], 0x00110 ); + regop( &i.operand[1], 0x00100 ); + regop( &i.operand[2], 0x00108 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PADD reserved segment does not dereference" ); + p = reg_u64( 0x00110 ); + CHECK( __cpu_ptr_segment( p ) == 0x0f, "PADD preserves reserved segment selector" ); + CHECK( __cpu_ptr_offset( p ) == 123, "PADD reserved segment offset" ); + + /* PSW is untouched by all pointer arithmetic instructions. */ + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PADD for PSW preservation" ); + CHECK( __cpu_get_psw() == psw, "PADD preserves PSW" ); + + /* 60-bit offset overflow/underflow faults are atomic. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_PTR_OFFSET_MASK ) == 0, "make max-offset PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, 1 ); + saved = __mpu_UINT64_C( 0x1122334455667788 ); + set_reg_u64( 0x00110, saved ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD offset overflow" ); + CHECK( reg_u64( 0x00110 ) == saved, "PADD overflow leaves destination unchanged" ); + + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0 ) == 0, "make zero-offset PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, -1 ); + set_reg_u64( 0x00110, saved ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD offset underflow" ); + CHECK( reg_u64( 0x00110 ) == saved, "PADD underflow leaves destination unchanged" ); + + /* INT64_MIN is handled without signed-overflow undefined behaviour. */ + minbits = __mpu_UINT64_C( 0x8000000000000000 ); + memcpy( &mindelta, &minbits, sizeof( mindelta ) ); + set_reg_i64( 0x00108, mindelta ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD INT64_MIN displacement faults cleanly" ); + + /* PDIFF requires equal segment selectors and is also atomic on fault. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 64 ) == 0, "make DATA pointer for PDIFF" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 16 ) == 0, "make STACK pointer for PDIFF" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, saved ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PDIFF; + regop( &i.operand[0], 0x00110 ); + regop( &i.operand[1], 0x00100 ); + regop( &i.operand[2], 0x00108 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PDIFF rejects different segments" ); + CHECK( reg_u64( 0x00110 ) == saved, "PDIFF segment fault leaves destination unchanged" ); + + /* Same-segment PDIFF may be negative and still fits signed integer64. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 10 ) == 0, "make PROGRAM pointer low" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 42 ) == 0, "make PROGRAM pointer high" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PDIFF PROGRAM pointers" ); + CHECK( reg_i64( 0x00110 ) == -32, "PDIFF signed negative result" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu PTR64 arithmetic regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-pointer-policy.c b/tests/tst-pointer-policy.c new file mode 100644 index 0000000..15d3b0f --- /dev/null +++ b/tests/tst-pointer-policy.c @@ -0,0 +1,152 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned bits, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = address; +} + +static void +set_u64( unsigned address, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), v ); +} + +static __mpu_uint64_t +get_u64( unsigned address ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); +} + +static int +exec2( cpu_opcode op, unsigned a, unsigned b ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = op; + regop( &i.operand[0], 64, a ); + regop( &i.operand[1], 64, b ); + return( __cpu_exec( &i ) ); +} + +static int +exec3( cpu_opcode op, unsigned d, unsigned a, unsigned b ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = op; + regop( &i.operand[0], 64, d ); + regop( &i.operand[1], 64, a ); + regop( &i.operand[2], 64, b ); + return( __cpu_exec( &i ) ); +} + +int +main( void ) +{ + __mpu_uint64_t p, q, saved; + __mpu_UNIT_t code[2] = + { + 0, 0 + }; + cpu_instruction i; + + __cpu_init(); + CHECK( CPU_ABI_STACK_RED_ZONE_SIZE == 0U, "ABI red zone is zero bytes" ); + CHECK( __cpu_data_alloc( 128 ) == 0, "DATA alloc" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "STACK alloc" ); + CHECK( __cpu_program_set( code, sizeof( code ) ) == 0, "PROGRAM set" ); + + /* Pointer equality is full 64-bit equality and can be tested with ICMP/ZF. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 32 ) == 0, "make DATA pointer p" ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 32 ) == 0, "make equal DATA pointer q" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec2( CPU_OP_ICMP, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "ICMP equal pointers" ); + CHECK( ( __cpu_get_psw() & PSW_ZF_MASK ) != 0, "equal PTR64 sets ZF" ); + + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK pointer q" ); + set_u64( 0x108, q ); + CHECK( exec2( CPU_OP_ICMP, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "ICMP cross-segment bit patterns" ); + CHECK( ( __cpu_get_psw() & PSW_ZF_MASK ) == 0, "different segments are not equal" ); + + /* Pointer ordering is defined by PDIFF only inside one selector. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 64 ) == 0, "make lower DATA pointer" ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 96 ) == 0, "make upper DATA pointer" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "same-segment PDIFF" ); + CHECK( (__mpu_int64_t)get_u64( 0x110 ) == -32, "PDIFF gives signed byte difference" ); + CHECK( exec2( CPU_OP_ITEST, 0x110, 0x110 ) == CPU_EXEC_SUCCESS, "ITEST pointer difference" ); + CHECK( ( __cpu_get_psw() & PSW_SF_MASK ) != 0, "negative pointer difference sets SF" ); + + saved = __mpu_UINT64_C( 0x1122334455667788 ); + set_u64( 0x110, saved ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_STACK, 96 ) == 0, "make cross-segment pointer" ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_POINTER_FAULT, "PDIFF rejects cross-segment ordering" ); + CHECK( get_u64( 0x110 ) == saved, "PDIFF cross-segment fault is atomic" ); + + /* Reserved selectors are structurally valid and support non-dereferencing arithmetic. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 123 ) == 0, "make reserved pointer p" ); + CHECK( __cpu_ptr_make( &q, 0x0f, 100 ) == 0, "make reserved pointer q" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "reserved same-selector PDIFF" ); + CHECK( (__mpu_int64_t)get_u64( 0x110 ) == 23, "reserved PDIFF difference" ); + + /* A reserved PTR64 may be stored as data but may not be dereferenced. */ + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_STORE; + i.operand[0].type = CPU_OPERAND_ADDR64; + i.operand[0].value.addr64 = 16; + regop( &i.operand[1], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "STORE preserves reserved PTR64 as ordinary data" ); + CHECK( __cpu_data_get_u64( __cpu_data_ptr() + 16 ) == p, "stored reserved PTR64 bits" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + regop( &i.operand[0], 64, 0x118 ); + regop( &i.operand[1], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "LOADP rejects reserved selector" ); + + /* Even a zero-length BMOVI requires semantically valid address-space selectors. */ + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 16 ) == 0, "make valid DATA pointer" ); + set_u64( 0x108, q ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOVI; + regop( &i.operand[0], 64, 0x108 ); + regop( &i.operand[1], 64, 0x100 ); + i.operand[2].type = CPU_OPERAND_IMM64; + i.operand[2].value.imm64 = 0; + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "zero-length BMOVI rejects reserved source" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_JMPP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects reserved selector" ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALLP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "CALLP rejects reserved selector" ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETSP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "SETSP rejects reserved selector" ); + + /* DATA offsets 1..15 are ABI-reserved guard values, but only all-zero PTR64 is NULL. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 1 ) == 0, "make DATA guard pointer" ); + CHECK( p != CPU_NULL_PTR, "DATA:1 is not NULL" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0 ) == 0 && p == CPU_NULL_PTR, "DATA:0 is canonical NULL" ); + + __cpu_free_context(); + puts( "libcpu pointer-comparison/red-zone/reserved-segment policy regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-pointer.c b/tests/tst-pointer.c new file mode 100644 index 0000000..ca0ff99 --- /dev/null +++ b/tests/tst-pointer.c @@ -0,0 +1,154 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".data\n" +".align 8\n" +"global_value:\n" +" .integer 64, 41\n" +".text\n" +" LEA r64@0x00000, global_value\n" +" LOADP r64@0x00008, r64@0x00000\n" +" LDI r64@0x00010, 42\n" +" STOREP r64@0x00000, r64@0x00010\n" +" LOADP r64@0x00018, r64@0x00000\n" +" ADJSP -16\n" +" LDI r64@0x00020, 11\n" +" LDI r64@0x00028, 22\n" +" STSP 0, r64@0x00020\n" +" STSP 8, r64@0x00028\n" +" LEASP r64@0x00030, 0\n" +" LOADP r64@0x00038, r64@0x00030\n" +" LEASP r64@0x00040, 8\n" +" LOADP r64@0x00048, r64@0x00040\n" +" LDI r64@0x00050, 33\n" +" STOREP r64@0x00040, r64@0x00050\n" +" LDSP r64@0x00058, 8\n" +" ADJSP 16\n" +" HALT\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), 8 ); + return( v ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p; + __mpu_UNIT_t saved[8]; + char err[256]; + int rc; + + __cpu_init(); + + CHECK( CPU_PTR_SEG_SHIFT == 60U, "PTR64 segment shift" ); + CHECK( CPU_PTR_OFFSET_MASK == __mpu_UINT64_C( 0x0fffffffffffffff ), "PTR64 offset mask" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0x1234 ) == 0, "make DATA pointer" ); + CHECK( p == __mpu_UINT64_C( 0x0000000000001234 ), "DATA pointer encoding" ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA, "DATA pointer segment" ); + CHECK( __cpu_ptr_offset( p ) == 0x1234, "DATA pointer offset" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 0x1234 ) == 0, "make STACK pointer" ); + CHECK( p == __mpu_UINT64_C( 0x1000000000001234 ), "STACK pointer encoding" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0x1234 ) == 0, "make PROGRAM pointer" ); + CHECK( p == __mpu_UINT64_C( 0x2000000000001234 ), "PROGRAM pointer encoding" ); + CHECK( __cpu_ptr_make( &p, 0x10, 0 ) == -1, "reject segment wider than four bits" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_PTR_OFFSET_MASK + 1 ) == -1, "reject offset wider than 60 bits" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "PTR64 program run" ); + + p = reg_u64( 0x00000 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA, "LEA creates DATA pointer" ); + CHECK( __cpu_ptr_offset( p ) == CPU_DATA_NULL_GUARD_SIZE, "LEA DATA label offset after NULL guard" ); + CHECK( eq64( 0x00008, "41" ), "LOADP reads DATA" ); + CHECK( eq64( 0x00018, "42" ), "STOREP writes DATA and LOADP reads it back" ); + CHECK( eq64( 0x00038, "11" ), "LEASP/LOADP first local object field" ); + CHECK( eq64( 0x00048, "22" ), "LEASP/LOADP second local object field" ); + CHECK( eq64( 0x00058, "33" ), "STOREP writes STACK through PTR64" ); + CHECK( __cpu_sp == 128, "stack frame released" ); + + p = reg_u64( 0x00030 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_STACK, "LEASP segment selector" ); + CHECK( __cpu_ptr_offset( p ) == 112, "LEASP absolute stack offset for SP+0" ); + + /* PROGRAM is readable through PTR64 but is not writable. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM pointer for indirect access" ); + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 16; + i.operand[0].value.reg.address = 0x00068; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "LOADP may read PROGRAM" ); + CHECK( !memcmp( __cpu_reg_ptr( 16, 0x00068 ), __cpu_program_ptr(), 2 ), "LOADP PROGRAM bytes" ); + + memcpy( saved, __cpu_program_ptr(), sizeof( saved ) ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_STOREP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 64; + i.operand[0].value.reg.address = 0x00060; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00010; + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "STOREP rejects PROGRAM write" ); + CHECK( !memcmp( saved, __cpu_program_ptr(), sizeof( saved ) ), "PROGRAM write fault is atomic" ); + + + /* ABI NULL is all-zero PTR64 and is never dereferenceable. */ + p = CPU_NULL_PTR; + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 8; + i.operand[0].value.reg.address = 0x00070; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "NULL PTR64 dereference faults" ); + + /* Reserved segment selectors decode but cannot currently be dereferenced. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_FIRST_RESERVED, 0 ) == 0, "reserved segment fits PTR64" ); + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 8; + i.operand[0].value.reg.address = 0x00070; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + rc = __cpu_exec( &i ); + CHECK( rc == CPU_EXEC_POINTER_FAULT, "reserved PTR64 segment faults on dereference" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu PTR64/indirect-addressing regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-psw-abi-runner.sh b/tests/tst-psw-abi-runner.sh new file mode 100755 index 0000000..37fb5fa --- /dev/null +++ b/tests/tst-psw-abi-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-psw-abi-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/psw-call-abi.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-psw-abi.c b/tests/tst-psw-abi.c new file mode 100644 index 0000000..9e34ef9 --- /dev/null +++ b/tests/tst-psw-abi.c @@ -0,0 +1,133 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",(m)); return 1; } } while(0) + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = address; + return( o ); +} + +static void +put_u64_reg( unsigned int address, __mpu_uint64_t value ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &value, sizeof( value ) ); +} + +int +main( void ) +{ + cpu_instruction i; + __mpu_uint32_t before, after, control_before; + __mpu_uint64_t target, ret; + __mpu_UNIT_t *a, *b, *d; + + __cpu_init(); + + CHECK( CPU_PSW_ABI_VOLATILE_STATUS_MASK == PSW_MPU_MASK, + "volatile PSW domain is exactly PSW[15:0]"); + CHECK( CPU_PSW_ABI_PERSISTENT_CONTROL_MASK == + ( PSW_TF_MASK | PSW_IF_MASK | PSW_MODE_MASK ), + "persistent PSW domain is TF/IF/MODE"); + CHECK( CPU_PSW_ABI_EXECUTION_STATE_MASK == PSW_HF_MASK, + "HF is the execution-state PSW domain"); + CHECK( ( CPU_PSW_ABI_VOLATILE_STATUS_MASK & + CPU_PSW_ABI_PERSISTENT_CONTROL_MASK)==0, + "volatile and persistent PSW domains do not overlap"); + CHECK( ( CPU_PSW_ABI_VOLATILE_STATUS_MASK | + CPU_PSW_ABI_PERSISTENT_CONTROL_MASK | + CPU_PSW_ABI_EXECUTION_STATE_MASK)==PSW_USED_MASK, + "PSW ABI domains cover all currently used PSW bits"); + + CHECK( __cpu_program_alloc( 64 ) == 0, "PROGRAM allocation" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "STACK allocation" ); + + /* CALL/RET are instructions, not implicit PSW save/restore operations. + Their own execution must leave every currently used PSW bit untouched. */ + before = ( PSW_AF_MASK | PSW_CF_MASK | PSW_ZF_MASK | PSW_INXF_MASK | + PSW_HF_MASK | PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)2U << PSW_MODE_SHIFT )); + __cpu_set_psw( before ); + before = __cpu_get_psw(); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALL; + i.operand[0].type = CPU_OPERAND_CODE64; + i.operand[0].value.code64 = 32; + __cpu_pc = 4; + __cpu_sp = 128; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "CALL executes" ); + CHECK( __cpu_get_psw() == before, "CALL itself is PSW-neutral" ); + CHECK( __cpu_sp == 120, "CALL pushes return PC" ); + memcpy( &ret, __cpu_stack_ptr() + 120, sizeof( ret ) ); + CHECK( ret == 14, "CALL raw return PC" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RET; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "RET executes" ); + CHECK( __cpu_get_psw() == before, "RET itself is PSW-neutral" ); + CHECK( __cpu_pc == 14 && __cpu_sp == 128, "RET restores PC/SP call state" ); + + /* CALLP has the same PSW-neutral instruction semantics. */ + CHECK( __cpu_ptr_make( &target, CPU_PTR_SEG_PROGRAM, 40 ) == 0, + "make PROGRAM PTR64"); + put_u64_reg( 0x00100, target ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALLP; + i.operand[0] = reg_operand( 64, 0x00100 ); + __cpu_pc = 8; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "CALLP executes" ); + CHECK( __cpu_get_psw() == before, "CALLP itself is PSW-neutral" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RET; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "RET after CALLP executes" ); + CHECK( __cpu_get_psw() == before, "RET after CALLP is PSW-neutral" ); + CHECK( __cpu_pc == 13 && __cpu_sp == 128, "CALLP/RET restore PC/SP call state" ); + + /* Arithmetic status is caller-clobbered, while TF/IF/MODE are persistent + control state. IADD is sufficient to demonstrate both domains. */ + a = __cpu_reg_ptr( 8, 0x00200 ); + b = __cpu_reg_ptr( 8, 0x00201 ); + d = __cpu_reg_ptr( 8, 0x00202 ); + CHECK( a && b && d, "IADD register views" ); + *a = 1; + *b = 1; + *d = 0; + + __cpu_set_psw( PSW_CF_MASK | PSW_ZF_MASK | PSW_INXF_MASK | + PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)3U << PSW_MODE_SHIFT )); + before = __cpu_get_psw(); + control_before = before & CPU_PSW_ABI_PERSISTENT_CONTROL_MASK; + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_IADD; + i.operand[0] = reg_operand( 8, 0x00202 ); + i.operand[1] = reg_operand( 8, 0x00200 ); + i.operand[2] = reg_operand( 8, 0x00201 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "IADD executes" ); + after = __cpu_get_psw(); + CHECK( *d == 2, "IADD result" ); + CHECK( ( after & CPU_PSW_ABI_PERSISTENT_CONTROL_MASK ) == control_before, + "ordinary arithmetic preserves TF/IF/MODE control domain"); + CHECK( ( after & CPU_PSW_ABI_VOLATILE_STATUS_MASK ) != + ( before & CPU_PSW_ABI_VOLATILE_STATUS_MASK ), + "arithmetic may clobber volatile PSW[15:0]"); + + __cpu_free_context(); + puts( "libcpu PSW calling-convention regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-psw-write.c b/tests/tst-psw-write.c new file mode 100644 index 0000000..0410ec6 --- /dev/null +++ b/tests/tst-psw-write.c @@ -0,0 +1,255 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +static void +clear_instruction( cpu_instruction *instruction, cpu_opcode opcode ) +{ + memset( instruction, 0, sizeof(*instruction) ); + instruction->opcode = opcode; +} + + +static cpu_operand +psw_operand( unsigned int bit ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_PSWBIT8; + operand.value.pswbit8 = bit; + + return( operand ); +} + + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_REGISTER; + operand.value.reg.bits = bits; + operand.value.reg.address = address; + + return( operand ); +} + + +static void +check_assembler_contract( void ) +{ + static const char good_source[] = + ".text\n" + "SETF CF\n" + "CLRF AF\n" + "CLRCPUF\n" + "CLRMPUF\n" + "HALT\n"; + static const char *bad_source[] = + { + ".text\nSETF HF\n", + ".text\nSETF TF\n", + ".text\nSETF IF\n", + ".text\nCLRF HF\n", + ".text\nCLRF TF\n", + ".text\nCLRF IF\n" + }; + cpu_asm_image image; + cpu_instruction instruction; + char err[256]; + __mpu_uint64_t pc = 0; + unsigned int i; + + memset( &image, 0, sizeof(image) ); + CHECK( cpu_assemble_text( good_source, &image, err, sizeof(err) ) == 0, + "assemble writable PSW flag instructions" ); + if( image.code ) + { + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode SETF" ); + CHECK( instruction.opcode == CPU_OP_SETF, "SETF opcode" ); + CHECK( instruction.operand[0].type == CPU_OPERAND_PSWBIT8 && + instruction.operand[0].value.pswbit8 == CPU_PSWBIT_CF, + "SETF selector" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRF" ); + CHECK( instruction.opcode == CPU_OP_CLRF, "CLRF opcode" ); + CHECK( instruction.operand[0].value.pswbit8 == CPU_PSWBIT_AF, + "CLRF selector" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRCPUF" ); + CHECK( instruction.opcode == CPU_OP_CLRCPUF, "CLRCPUF opcode" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRMPUF" ); + CHECK( instruction.opcode == CPU_OP_CLRMPUF, "CLRMPUF opcode" ); + } + cpu_asm_image_free( &image ); + + for( i = 0; i < sizeof(bad_source) / sizeof(bad_source[0]); ++i ) + { + memset( &image, 0, sizeof(image) ); + CHECK( cpu_assemble_text( bad_source[i], &image, err, sizeof(err) ) != 0, + "assembler rejects control/execution PSW selector write" ); + cpu_asm_image_free( &image ); + } +} + + +int +main( void ) +{ + cpu_instruction instruction; + __mpu_uint32_t psw, expected; + unsigned int bit; + + __cpu_init(); + + CHECK( CPU_OP_SETF == 0x0010, "SETF OPCODE16" ); + CHECK( CPU_OP_CLRF == 0x0011, "CLRF OPCODE16" ); + CHECK( CPU_OP_CLRCPUF == 0x0012, "CLRCPUF OPCODE16" ); + CHECK( CPU_OP_CLRMPUF == 0x0013, "CLRMPUF OPCODE16" ); + CHECK( PSW_CPUF_MASK == __mpu_UINT32_C(0x000000ff), "CPU flag mask" ); + CHECK( PSW_MPUF_MASK == __mpu_UINT32_C(0x0000ff00), "MPU flag mask" ); + + /* SETF/CLRF write exactly one arithmetic/status flag in PSW[15:0]. */ + for( bit = CPU_PSWBIT_AF; bit <= CPU_PSWBIT_WRITE_MAX; ++bit ) + { + __mpu_uint32_t mask = (__mpu_uint32_t)1U << bit; + + __cpu_set_psw( PSW_TF_MASK | PSW_IF_MASK | (__mpu_uint32_t)(2U << PSW_MODE_SHIFT) ); + psw = __cpu_get_psw(); + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF return code" ); + CHECK( __cpu_get_psw() == ( psw | mask ), "SETF changes only selected flag" ); + + psw = __cpu_get_psw(); + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRF return code" ); + CHECK( __cpu_get_psw() == ( psw & ~mask ), "CLRF changes only selected flag" ); + } + + /* HF/TF/IF are readable by branches but are not writable through SETF/CLRF. */ + for( bit = CPU_PSWBIT_HF; bit <= CPU_PSWBIT_IF; ++bit ) + { + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "SETF rejects HF/TF/IF" ); + + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "CLRF rejects HF/TF/IF" ); + } + + /* CLRCPUF clears only PSW[7:0]. */ + __cpu_set_psw( PSW_USED_MASK ); + psw = __cpu_get_psw(); + expected = psw & ~PSW_CPUF_MASK; + clear_instruction( &instruction, CPU_OP_CLRCPUF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRCPUF return code" ); + CHECK( __cpu_get_psw() == expected, "CLRCPUF preserves PSW[15:8] and control state" ); + + /* CLRMPUF clears only PSW[15:8]. */ + __cpu_set_psw( PSW_USED_MASK ); + psw = __cpu_get_psw(); + expected = psw & ~PSW_MPUF_MASK; + clear_instruction( &instruction, CPU_OP_CLRMPUF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRMPUF return code" ); + CHECK( __cpu_get_psw() == expected, "CLRMPUF preserves PSW[7:0] and control state" ); + + /* SETF/CLRF provide an explicit initial carry for LibMPU IADC. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00600 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00601 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00602 ); + + *a = 1; + *b = 2; + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF CF before IADC" ); + + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00600 ); + instruction.operand[1] = reg_operand( 8, 0x00601 ); + instruction.operand[2] = reg_operand( 8, 0x00602 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC after SETF CF" ); + CHECK( *dst == 4, "IADC consumes SETF CF" ); + + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRF CF before IADC" ); + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00600 ); + instruction.operand[1] = reg_operand( 8, 0x00601 ); + instruction.operand[2] = reg_operand( 8, 0x00602 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC after CLRF CF" ); + CHECK( *dst == 3, "IADC consumes cleared CF" ); + } + + /* SETF CF likewise provides the input borrow consumed by LibMPU ISBB. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00610 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00611 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00612 ); + + *a = 9; + *b = 3; + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF CF before ISBB" ); + + clear_instruction( &instruction, CPU_OP_ISBB ); + instruction.operand[0] = reg_operand( 8, 0x00610 ); + instruction.operand[1] = reg_operand( 8, 0x00611 ); + instruction.operand[2] = reg_operand( 8, 0x00612 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISBB after SETF CF" ); + CHECK( *dst == 5, "ISBB consumes SETF CF" ); + } + + check_assembler_contract(); + + __cpu_free_context(); + + if( failures ) + { + fprintf( stderr, "LibMCPU PSW write regression: %d failure(s)\n", failures ); + return( 1 ); + } + + puts( "LibMCPU PSW write regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-readelf-runner.sh b/tests/tst-readelf-runner.sh new file mode 100755 index 0000000..cf14a1d --- /dev/null +++ b/tests/tst-readelf-runner.sh @@ -0,0 +1,69 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_READELF:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-readelf-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` + +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/liba.o" "$EXAMPLES/dynamic-liba.s" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/liba.o" + +"$CPU_READELF" -h -S -s -r "$TMP/main.o" > "$TMP/rel.txt" +grep -q 'REL (Relocatable file)' "$TMP/rel.txt" +grep -q "Symbol table '.symtab'" "$TMP/rel.txt" +grep -q "Relocation section '.rela.text'" "$TMP/rel.txt" +grep -q 'R_MCPU_CODE64' "$TMP/rel.txt" + +"$CPU_READELF" -a "$TMP/main" > "$TMP/exec.txt" +grep -q 'ABI Version: 3' "$TMP/exec.txt" +grep -q 'EXEC (Executable file)' "$TMP/exec.txt" +grep -q 'Program Headers:' "$TMP/exec.txt" +grep -q 'MCPU_STACK' "$TMP/exec.txt" +grep -q "Symbol table '.dynsym'" "$TMP/exec.txt" +grep -q 'MCPU target attributes:' "$TMP/exec.txt" +grep -q 'MCPU lifecycle attributes:' "$TMP/exec.txt" +grep -q "Dynamic section '.dynamic'" "$TMP/exec.txt" +grep -q 'NULL' "$TMP/exec.txt" + +"$CPU_READELF" -hSlrsdA "$TMP/liba.so" > "$TMP/dyn.txt" +grep -q 'DYN (Shared object file)' "$TMP/dyn.txt" +grep -q "Symbol table '.dynsym'" "$TMP/dyn.txt" +grep -q 'foo' "$TMP/dyn.txt" +grep -q 'MCPU target attributes:' "$TMP/dyn.txt" +grep -q 'ABI Version: 3' "$TMP/dyn.txt" +grep -q "Dynamic section '.dynamic'" "$TMP/dyn.txt" +grep -q 'SONAME' "$TMP/dyn.txt" +grep -q 'Library soname: \[liba.so.1\]' "$TMP/dyn.txt" + +# Section Headers are intentionally wide: one section occupies exactly one +# physical output line rather than a wrapped two-line record. +"$CPU_READELF" -S "$TMP/main" > "$TMP/sections.txt" +grep -q '^ \[Nr\] Name.*Size.*EntSize.*Flags Link Info Align$' "$TMP/sections.txt" +section_count=`grep -c '^ \[[ 0-9][ 0-9]\] ' "$TMP/sections.txt"` +test "$section_count" -gt 0 +if grep -q '^ [0-9a-f][0-9a-f]* ' "$TMP/sections.txt"; then + echo "mcpu-readelf unexpectedly wrapped a section header" >&2 + exit 1 +fi + +"$CPU_READELF" --version | grep -Fq "$PACKAGE_VERSION" +"$CPU_READELF" --help | grep -q -- '--arch-specific' +"$CPU_READELF" --help | grep -q -- '--dynamic' + +printf 'not MELF\n' > "$TMP/bad" +if "$CPU_READELF" -h "$TMP/bad" >/dev/null 2>&1; then + echo "mcpu-readelf unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-registers.c b/tests/tst-registers.c new file mode 100644 index 0000000..c8b1c99 --- /dev/null +++ b/tests/tst-registers.c @@ -0,0 +1,128 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +struct width_test +{ + unsigned int bits; + __mpu_size_t bytes; +}; + + +int +main( void ) +{ + static const struct width_test widths[] = + { + { 8, 1 }, + { 16, 2 }, + { 32, 4 }, + { 64, 8 }, + { 128, 16 }, + { 256, 32 }, + { 512, 64 }, + { 1024, 128 }, + { 2048, 256 }, + { 4096, 512 }, + { 8192, 1024 }, + { 16384, 2048 }, + { 32768, 4096 }, + { 65536, 8192 } + }; + + __mpu_size_t i; + + __cpu_init(); + + CHECK( CPU_REGISTERS_MEM_SIZE_IN_UNITS == 524288, + "512 KiB register file size" ); + CHECK( CPU_REGISTER_ADDRESS_MAX == 0x0007ffffU, + "register address maximum" ); + CHECK( CPU_REGISTER_MIN_BITS == 8, + "minimum register width" ); + CHECK( CPU_REGISTER_MAX_BITS == 65536, + "maximum register width" ); + + for( i = 0; i < sizeof( widths ) / sizeof( widths[0] ); ++i ) + { + __mpu_uint32_t last; + + CHECK( __cpu_reg_size( widths[i].bits ) == widths[i].bytes, + "register width size" ); + + CHECK( __cpu_reg_valid( widths[i].bits, 0 ) == 1, + "register at address zero" ); + + CHECK( __cpu_reg_ptr( widths[i].bits, 0 ) == __cpu_registers, + "register pointer at address zero" ); + + last = (__mpu_uint32_t)(CPU_REGISTERS_MEM_SIZE_IN_UNITS - widths[i].bytes); + + CHECK( __cpu_reg_valid( widths[i].bits, last ) == 1, + "last naturally aligned register" ); + + CHECK( __cpu_reg_ptr( widths[i].bits, last ) == &__cpu_registers[last], + "last register pointer" ); + + if( widths[i].bytes > 1 ) + { + CHECK( __cpu_reg_valid( widths[i].bits, last + 1 ) == 0, + "natural alignment check" ); + CHECK( __cpu_reg_ptr( widths[i].bits, last + 1 ) == 0, + "unaligned register pointer" ); + } + } + + CHECK( __cpu_reg_size( 0 ) == 0, "zero register width" ); + CHECK( __cpu_reg_size( 7 ) == 0, "unsupported 7-bit register width" ); + CHECK( __cpu_reg_size( 24 ) == 0, "unsupported 24-bit register width" ); + CHECK( __cpu_reg_size( 131072 ) == 0, "register width above 65536" ); + + CHECK( __cpu_reg_valid( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1 ) == 1, + "last byte is a valid r8" ); + CHECK( __cpu_reg_valid( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS ) == 0, + "address one byte beyond register file" ); + CHECK( __cpu_reg_ptr( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS ) == 0, + "pointer beyond register file" ); + + CHECK( __cpu_reg_valid( 32, 12 ) == 1, + "r32 naturally aligned address" ); + CHECK( __cpu_reg_valid( 32, 13 ) == 0, + "r32 unaligned address" ); + + CHECK( __cpu_reg_valid( 65536, 0x0007e000U ) == 1, + "last r65536 region" ); + CHECK( __cpu_reg_valid( 65536, 0x0007e001U ) == 0, + "unaligned r65536 region" ); + CHECK( __cpu_reg_valid( 65536, 0x00080000U ) == 0, + "r65536 beyond register file" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu register regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 register width/alignment/bounds regression: PASS\n" ); + printf( "register widths: r8 ... r65536\n" ); + printf( "register address range: 0x00000 ... 0x7ffff\n" ); + + return( 0 ); +} diff --git a/tests/tst-relocatable-runner.sh b/tests/tst-relocatable-runner.sh new file mode 100755 index 0000000..20d1c35 --- /dev/null +++ b/tests/tst-relocatable-runner.sh @@ -0,0 +1,75 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" +: "${CPU_LD:?}" +: "${CPU_RUN:?}" +: "${TEST_SRCDIR:?}" + +D="${TMPDIR:-/tmp}/libcpu-reloc-$$" +trap 'rm -rf "$D"' EXIT HUP INT TERM +mkdir -p "$D" + +cat > "$D/main.s" <<'EOS' +.global _start +.global _life +.extern add_two +.extern shared +.text +_start_life: + LDI r64@0x00000, 40 + CALL add_two + STORE shared, r64@0x00000 + LOAD r64@0x00018, shared + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$D/lib.s" <<'EOS' +.global add_two +.global shared +.data +.align 32 +shared: + .integer 64, 0 +raw_bytes: + .byte 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x00 + .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + .byte 0x00, 0x00, 0xd4, 0x37 +.text +add_two: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$D/main.o" "$D/main.s" >/dev/null +"$CPU_AS" "$D/lib.s" -o "$D/lib.o" >/dev/null +"$CPU_LD" --stack-size=512 --heap-size=1M --heap-limit=8G -o "$D/a" "$D/main.o" "$D/lib.o" >/dev/null +"$CPU_LD" --stack-size=1KiB --heap-size=1MiB --heap-limit=1GiB -o "$D/a-units" "$D/main.o" "$D/lib.o" >/dev/null +if "$CPU_LD" --stack-size=1KiB --heap-size=-1M --heap-limit=1GiB -o "$D/a-bad" "$D/main.o" "$D/lib.o" >/dev/null 2>&1; then + echo "mcpu-ld accepted negative heap size" >&2 + exit 1 +fi +if "$CPU_LD" --stack-size=1KiB --heap-size=1B --heap-limit=1GiB -o "$D/a-bad" "$D/main.o" "$D/lib.o" >/dev/null 2>&1; then + echo "mcpu-ld accepted unsupported B suffix" >&2 + exit 1 +fi +v=`"$CPU_RUN" --int-reg=r64@0x00018 "$D/a"` +test "x$v" = x42 + +# DATA integer output is target-representation aware. +v=`"$CPU_RUN" --int-data=64@32 "$D/a"` +test "x$v" = x42 + +# raw_bytes starts at DATA offset 40. Request beyond the end of .data: +# cpu-run must clip the dump to the 20 bytes that remain in .data. +out=`"$CPU_RUN" --raw-data=64@40 "$D/a"` +printf '%s\n' "$out" | grep -F 'DATA memory dump: (20 bytes):' >/dev/null +printf '%s\n' "$out" | grep -F ' 0000 : 02 00 01 00 01 00 40 00 00 00 00 00 00 00 00 00 | ......@.........' >/dev/null +printf '%s\n' "$out" | grep -F ' 0010 : 00 00 d4 37 ' >/dev/null + +# The linker-owned DATA NULL guard is not part of .data and must not be dumpable. +if "$CPU_RUN" --raw-data=1@16 "$D/a" >/dev/null 2>&1; then + echo 'raw DATA dump escaped the linked .data section' >&2 + exit 1 +fi diff --git a/tests/tst-relocatable.c b/tests/tst-relocatable.c new file mode 100644 index 0000000..a2df8c4 --- /dev/null +++ b/tests/tst-relocatable.c @@ -0,0 +1,326 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "mcpu-asm.h" +#include "mcpu-link.h" + +static int +sym( const cpu_asm_object *o, const char *n, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) if( !strcmp( o->symbols[i].name, n ) ) + { + *idx = i; + return( 0 ); + } + return( -1 ); +} + + +static int +expect_link_fail( const char *a, const char *b, const char *needle ) +{ + cpu_asm_object o[2]; + cpu_asm_image im; + char err[512]; + int ok = 0; + memset( o, 0, sizeof( o ) ); + memset( &im, 0, sizeof( im ) ); + if( cpu_assemble_object( a, &o[0], err, sizeof( err ) ) ) + { + fprintf( stderr, "negative object A did not assemble: %s\n", err ); + goto out; + } + if( cpu_assemble_object( b, &o[1], err, sizeof( err ) ) ) + { + fprintf( stderr, "negative object B did not assemble: %s\n", err ); + goto out; + } + if( !cpu_link_objects( o, 2, &im, err, sizeof( err ) ) ) + { + fprintf( stderr, "negative link unexpectedly succeeded\n" ); + cpu_asm_image_free( &im ); + goto out; + } + if( needle && !strstr( err, needle ) ) + { + fprintf( stderr, "negative link error mismatch: %s\n", err ); + goto out; + } + ok = 1; +out: + cpu_asm_object_free( &o[0] ); + cpu_asm_object_free( &o[1] ); + return( ok ? 0 : -1 ); +} + +int +main( void ) +{ + const char *main_src= + ".global _start, _life\n" + ".extern add_two\n" + ".extern shared\n" + ".text\n" + "_start_life:\n" + " LDI r64@0x00000, 40\n" + " CALL add_two\n" + " STORE shared, r64@0x00000\n" + " LOAD r64@0x00018, shared\n" + " LEA r64@0x00020, shared+0\n" + " LDI r32@0x00030, 0\n" + " EXIT r32@0x00030\n"; + const char *lib_src= + ".global add_two\n" + ".global shared\n" + ".global shared_ptr\n" + ".global fn_ptr\n" + ".data\n" + ".align 32\n" + "shared:\n" + " .integer 64, 0\n" + "shared_ptr:\n" + " .ptr shared\n" + "fn_ptr:\n" + " .cptr add_two\n" + ".text\n" + "add_two:\n" + " LDI r64@0x00008, 2\n" + " IADD r64@0x00000, r64@0x00000, r64@0x00008\n" + " RET\n"; + cpu_asm_object o[2]; + cpu_asm_image im; + char err[512]; + size_t i, ix; + __mpu_uint64_t p; + __mpu_init(); + memset( o, 0, sizeof( o ) ); + memset( &im, 0, sizeof( im ) ); + if( cpu_assemble_object( main_src, &o[0], err, sizeof( err ) ) ) + { + fprintf( stderr, "main object: %s\n", err ); + return( 1 ); + } + if( cpu_assemble_object( lib_src, &o[1], err, sizeof( err ) ) ) + { + fprintf( stderr, "lib object: %s\n", err ); + return( 1 ); + } + if( o[0].data_size != 0 ) + { + fprintf( stderr, "relocatable DATA unexpectedly contains NULL guard\n" ); + return( 1 ); + } + if( o[1].data_align != 32 ) + { + fprintf( stderr, "DATA alignment not preserved: %llu\n", (unsigned long long)o[1].data_align ); + return( 1 ); + } + if( sym( &o[0], "add_two", &ix ) || o[0].symbols[ix].section != CPU_OBJ_SEC_UNDEF ) + { + fprintf( stderr, "extern symbol missing\n" ); + return( 1 ); + } + if( sym( &o[1], "shared", &ix ) || o[1].symbols[ix].binding != CPU_OBJ_BIND_GLOBAL || o[1].symbols[ix].value != 0 ) + { + fprintf( stderr, "defined global shared bad\n" ); + return( 1 ); + } + if( o[0].reloc_count != 4 || o[1].reloc_count != 2 ) + { + fprintf( stderr, "unexpected relocation counts %zu %zu\n", o[0].reloc_count, o[1].reloc_count ); + return( 1 ); + } + if( cpu_link_objects( o, 2, &im, err, sizeof( err ) ) ) + { + fprintf( stderr, "link: %s\n", err ); + return( 1 ); + } + if( !im.has_life || im.life_entry != 0 ) + { + fprintf( stderr, "bad linked _life entry\n" ); + return( 1 ); + } + if( im.data_size != 56 ) + { + fprintf( stderr, "linked DATA size/alignment/guard mismatch: %zu\n", im.data_size ); + return( 1 ); + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; i++ ) if( im.data[i] ) + { + fprintf( stderr, "DATA guard is not zero\n" ); + return( 1 ); + } + /* shared is aligned at final DATA offset 32; shared_ptr immediately follows. */ + p = __cpu_data_get_u64( im.data + 40 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_DATA || __cpu_ptr_offset( p ) != 32 ) + { + fprintf( stderr, ".ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_data_get_u64( im.data + 48 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_PROGRAM ) + { + fprintf( stderr, ".cptr segment bad\n" ); + return( 1 ); + } + cpu_asm_image_free( &im ); + cpu_asm_object_free( &o[0] ); + cpu_asm_object_free( &o[1] ); + + /* _end is an executable linker-defined HEAP pointer at HEAP:16. */ + { + cpu_asm_object x = + { + 0 + }; + cpu_asm_image xi; + const char *src= + ".global _start, _life\n" + ".extern _end\n" + ".data\n" + "end_ptr:\n" + " .ptr _end\n" + ".text\n" + "_start_life:\n" + " LEA r64@0x00000, _end\n" + " LDI r32@0x00010, 0\n" + " EXIT r32@0x00010\n"; + memset( &xi, 0, sizeof( xi ) ); + if( cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end object: %s\n", err ); + return( 1 ); + } + if( cpu_link_objects( &x, 1, &xi, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end link: %s\n", err ); + return( 1 ); + } + if( xi.data_size < 24 ) + { + fprintf( stderr, "_end DATA image too small\n" ); + return( 1 ); + } + p = __cpu_data_get_u64( xi.data + CPU_DATA_NULL_GUARD_SIZE ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end .ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_code_get_u64( xi.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end LEA relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + cpu_asm_image_free( &xi ); + + memset( &xi, 0, sizeof( xi ) ); + if( cpu_link_objects_mode( &x, 1, &xi, CPU_LINK_DYNAMIC_EXEC, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end dynamic link: %s\n", err ); + return( 1 ); + } + if( xi.data_size < 24 ) + { + fprintf( stderr, "_end dynamic DATA image too small\n" ); + return( 1 ); + } + p = __cpu_data_get_u64( xi.data + CPU_DATA_NULL_GUARD_SIZE ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end dynamic .ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_code_get_u64( xi.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end dynamic LEA relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + if( xi.dyn_reloc_count != 0 ) + { + fprintf( stderr, "_end must not remain a dynamic relocation\n" ); + return( 1 ); + } + cpu_asm_image_free( &xi ); + cpu_asm_object_free( &x ); + } + + /* A hexadecimal absolute expression must not mistake the `x` in 0x20 for + a relocatable symbol. */ + { + cpu_asm_object x = + { + 0 + }; + const char *src= + ".global _start, _life\n" + ".text\n" + "_start_life:\n" + " LEA r64@0x00000, 0x20\n" + " LDI r32@0x00010, 0\n" + " EXIT r32@0x00010\n"; + if( cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, "hex absolute expression: %s\n", err ); + return( 1 ); + } + if( x.code_size < 13 ) + { + fprintf( stderr, "hex absolute expression produced short code\n" ); + return( 1 ); + } + p = __cpu_code_get_u64( x.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_DATA || __cpu_ptr_offset( p ) != 0x20 ) + { + fprintf( stderr, "hex absolute LEA bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + cpu_asm_object_free( &x ); + } + + /* .equ and relocatable global/extern symbols occupy different namespaces + and may not reuse one name. */ + { + cpu_asm_object x = + { + 0 + }; + const char *src= + ".equ N, 4*512\n" + ".global N\n" + ".global _start, _life\n" + ".text\n_start_life:\n LDI r32@0x00000, 0\n EXIT r32@0x00000\n"; + if( !cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, ".equ/global conflict unexpectedly accepted\n" ); + cpu_asm_object_free( &x ); + return( 1 ); + } + if( !strstr( err, "conflicts with .equ" ) ) + { + fprintf( stderr, ".equ/global conflict error mismatch: %s\n", err ); + return( 1 ); + } + } + + if( expect_link_fail( + ".global _start, _life\n.extern missing\n.text\n_start_life:\n CALL missing\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".data\nlocal:\n .integer 64,0\n", + "undefined symbol: missing")) return( 1 ); + + if( expect_link_fail( + ".global _start, _life, foo\n.data\nfoo:\n .integer 64,1\n.text\n_start_life:\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".global foo\n.data\nfoo:\n .integer 64,2\n", + "multiple definition of global symbol: foo")) return( 1 ); + + if( expect_link_fail( + ".global _start, _life\n.extern data_sym\n.data\np:\n .cptr data_sym\n.text\n_start_life:\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".global data_sym\n.data\ndata_sym:\n .integer 64,0\n", + ".cptr target is not PROGRAM: data_sym")) return( 1 ); + + __mpu_free_context(); + return( 0 ); +} diff --git a/tests/tst-run-memory-options.sh b/tests/tst-run-memory-options.sh new file mode 100755 index 0000000..ab176ce --- /dev/null +++ b/tests/tst-run-memory-options.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +base="${TMPDIR:-/tmp}/libmcpu-run-memory-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM + +cat > "$base.s" <<'EOS' +.data + .integer 64, 11 + .real 64, 1.5 + +.control + .integer 64, 21 + .real 64, 2.5 + +.input + .integer 64, 31 + .real 64, 3.5 + +.output + .integer 64, 41 + .real 64, 4.5 + +.trace + .integer 64, 51 + .real 64, 5.5 + +.text +_start_life: + HALT +EOS + +"$CPU_AS" -o "$base.o" "$base.s" >/dev/null +"$CPU_LD" -o "$base.melf" --stack-size=1K --heap-size=64K --heap-limit=1M \ + "$base.o" >/dev/null + +check_integer() +{ + option="$1" + expected="$2" + out=`"$CPU_RUN" "$option" "$base.melf"` + if test "$out" != "$expected"; then + echo "unexpected $option result: $out" >&2 + exit 1 + fi +} + +check_real() +{ + option="$1" + expected="$2" + out=`"$CPU_RUN" --digits=20 "$option" "$base.melf"` + case "$out" in + "$expected"*) ;; + *) echo "unexpected $option result: $out" >&2; exit 1 ;; + esac +} + +check_raw() +{ + option="$1" + memory="$2" + bytes="$3" + out=`"$CPU_RUN" "$option" "$base.melf"` + case "$out" in + "$memory memory dump: ($bytes bytes):"*) ;; + *) echo "unexpected $option dump: $out" >&2; exit 1 ;; + esac +} + +check_integer --int-data=64@16 11 +check_real --real-data=64@24 1.500000000000000E+0 +check_integer --int-control=64@0 21 +check_real --real-control=64@8 2.500000000000000E+0 +check_integer --int-heap=64@16 0 +check_real --real-heap=64@16 0.000000000000000E+0 +check_integer --int-input=64@0 31 +check_real --real-input=64@8 3.500000000000000E+0 +check_integer --int-output=64@0 41 +check_real --real-output=64@8 4.500000000000000E+0 +check_integer --int-trace=64@0 51 +check_real --real-trace=64@8 5.500000000000000E+0 + +# Exercise every accepted binary suffix while forcing clipping to 16 bytes. +check_raw --raw-data=1K@16 DATA 16 +check_raw --raw-control=1M@0 CONTROL 16 +check_raw --raw-input=1G@0 INPUT 16 +check_raw --raw-output=1KiB@0 OUTPUT 16 +check_raw --raw-trace=1MiB@0 TRACE 16 +check_raw --raw-heap=1GiB@65520 HEAP 16 + +# Segment/object bounds must reject an address at or beyond the end. +if "$CPU_RUN" --raw-control=1@16 "$base.melf" >/dev/null 2>&1; then + echo "--raw-control accepted address at CONTROL end" >&2 + exit 1 +fi +if "$CPU_RUN" --int-heap=64@65530 "$base.melf" >/dev/null 2>&1; then + echo "--int-heap accepted object crossing HEAP end" >&2 + exit 1 +fi +if "$CPU_RUN" --raw-data=1@32 "$base.melf" >/dev/null 2>&1; then + echo "--raw-data accepted address at .data end" >&2 + exit 1 +fi +if "$CPU_RUN" --raw-data=18446744073709551615GiB@16 "$base.melf" >/dev/null 2>&1; then + echo "--raw-data accepted overflowing byte count" >&2 + exit 1 +fi + +# The descriptions in --help intentionally begin in one vertical column. +help=`"$CPU_RUN" --help 2>&1` +for option in int-control real-control raw-control int-heap real-heap raw-heap \ + int-input real-input raw-input int-output real-output raw-output \ + int-trace real-trace raw-trace; do + echo "$help" | grep -- "--$option=" >/dev/null || exit 1 +done + +exit 0 diff --git a/tests/tst-runner.sh b/tests/tst-runner.sh new file mode 100755 index 0000000..54343b1 --- /dev/null +++ b/tests/tst-runner.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-radd-pi-$$" +trap 'rm -f "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/radd-pi.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --digits=60 --real-reg=r4096@0x00400 "$base.melf"` +case "$out" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected register result: $out" >&2; exit 1 ;; +esac +out=`"$CPU_RUN" --digits=60 --real-data=4096@16 "$base.melf"` +case "$out" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected DATA result: $out" >&2; exit 1 ;; +esac diff --git a/tests/tst-stack-runner.sh b/tests/tst-stack-runner.sh new file mode 100755 index 0000000..2bba333 --- /dev/null +++ b/tests/tst-stack-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-stack-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/stack.s" >/dev/null +"$CPU_LD" --stack-size=128 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00028 "$base.melf"` +test "x$out" = "x22" diff --git a/tests/tst-stack.c b/tests/tst-stack.c new file mode 100644 index 0000000..9c719ed --- /dev/null +++ b/tests/tst-stack.c @@ -0,0 +1,104 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" LDI r64@0x00000, 11\n" +" LDI r64@0x00008, 22\n" +" PUSH r64@0x00000\n" +" PUSH r64@0x00008\n" +" LDSP r64@0x00010, 8\n" +" STSP 0, r64@0x00000\n" +" POP r64@0x00018\n" +" POP r64@0x00020\n" +" ADJSP -16\n" +" STSP 0, r64@0x00008\n" +" LDSP r64@0x00028, 0\n" +" ADJSP 16\n" +" HALT\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + cpu_operand *o; + char err[256]; + __mpu_uint64_t sp; + __mpu_UNIT_t before[8]; + int rc; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 128 ) == 0, "allocate STACK" ); + CHECK( __cpu_stack_ptr() != 0, "STACK pointer" ); + CHECK( __cpu_stack_size() == 128, "STACK size" ); + CHECK( __cpu_sp == 128, "empty STACK starts at stack_size" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "stack bytecode produced" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_SUCCESS, "stack program run" ); + CHECK( __cpu_sp == 128, "SP restored after balanced stack operations" ); + CHECK( eq64( 16, "11" ), "LDSP reads SP+8" ); + CHECK( eq64( 24, "11" ), "STSP overwrites top before POP" ); + CHECK( eq64( 32, "11" ), "second POP value" ); + CHECK( eq64( 40, "22" ), "ADJSP frame with STSP/LDSP" ); + + /* PUSH overflow/underflow faults are atomic. */ + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PUSH; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 64; + i.operand[0].value.reg.address = 0; + __cpu_sp = 4; + memcpy( before, __cpu_stack_ptr(), sizeof( before ) ); + sp = __cpu_sp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "PUSH stack overflow fault" ); + CHECK( __cpu_sp == sp, "PUSH fault preserves SP" ); + CHECK( !memcmp( before, __cpu_stack_ptr(), sizeof( before ) ), "PUSH fault preserves STACK" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_POP; + o = &i.operand[0]; + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = 48; + memset( __cpu_reg_ptr( 64, 48 ), 0xa5, 8 ); + __cpu_sp = 128; + sp = __cpu_sp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "POP empty stack fault" ); + CHECK( __cpu_sp == sp, "POP fault preserves SP" ); + { + __mpu_UNIT_t a5[8]; + memset( a5, 0xa5, sizeof( a5 ) ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 48 ), a5, 8 ), "POP fault preserves destination" ); + } + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_ADJSP; + i.operand[0].type = CPU_OPERAND_OFF64; + i.operand[0].value.off64 = 1; + __cpu_sp = 128; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "ADJSP beyond stack end" ); + CHECK( __cpu_sp == 128, "ADJSP fault preserves SP" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + CHECK( __cpu_sp == 0, "SP cleared by free context" ); + puts( "libcpu stack/SP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-stage-actions-runner.sh b/tests/tst-stage-actions-runner.sh new file mode 100755 index 0000000..1deef01 --- /dev/null +++ b/tests/tst-stage-actions-runner.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +base="${TMPDIR:-/tmp}/libmcpu-stage-actions-$$" +trap 'rm -f "$base"*' EXIT HUP INT TERM + +# The published LIFE example: one scalar print action, two registers, one line. +"$CPU_AS" -o "$base-life.o" "$TEST_SRCDIR/fixtures/life-print.s" >/dev/null +"$CPU_LD" -o "$base-life" --stack-size=1K "$base-life.o" >/dev/null +out=`"$CPU_RUN" --steps=3 \ + --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ + -- "$base-life"` +expected='1 10 +2 20 +3 30' +test "x$out" = "x$expected" + +# AFTER_INIT also exists when the executable has no separate .init section. +out=`"$CPU_RUN" --steps=1 \ + --before-run=set-int-regs:r64@0x00000=77 \ + --after-init=print-int-regs:r64@0x00000 \ + --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ + -- "$base-life"` +expected='77 +1 10' +test "x$out" = "x$expected" + +# Explicit -- must make option-looking tokens ordinary guest argv strings. +"$CPU_AS" -o "$base-argv.o" "$TEST_SRCDIR/fixtures/argc-argv.s" >/dev/null +"$CPU_LD" -o "$base-argv" --stack-size=4K "$base-argv.o" >/dev/null +out=`"$CPU_RUN" --on-step=print-int-regs:r64@0x00000,r8@0x00020,r8@0x00021 \ + -- "$base-argv" --help -x` +test "x$out" = "x3 104 120" + +cat > "$base-model.s" <<'EOS' +.data + .real 64, 0.0 + +.input + .integer 64, 0 + +.output + .integer 64, 0 + .integer 64, 0 + +.init +_start: + END + +.text +_life: + END + JMP _life +EOS + +"$CPU_AS" -o "$base-model.o" "$base-model.s" >/dev/null +"$CPU_LD" -o "$base-model" --stack-size=1K "$base-model.o" >/dev/null + +# Multiple set/print objects, stage ordering and mpu_sscanf()-based values. +out=`"$CPU_RUN" --steps=1 --digits=20 \ + --before-run=set-int-input:64@0=100 \ + --before-run=set-real-data:64@16=1.5 \ + --after-init=print-int-input:64@0 \ + --after-init=print-real-data:64@16 \ + --on-step=set-int-output:64@0=41,64@8=42 \ + --on-step=print-int-output:64@0,64@8 \ + --after-run=print-int-output:64@0,64@8 \ + -- "$base-model"` +case "$out" in + "100 +1.500000000000000"*"E+0 +41 42 +41 42") ;; + *) echo "unexpected stage-action output:" >&2; echo "$out" >&2; exit 1 ;; +esac + +# AFTER_RUN follows FINI -> END on graceful EXIT. +cat > "$base-fini.s" <<'EOS' +.text +_start_life: + LDI r32@0x00008, 0 + EXIT r32@0x00008 + +.fini +_fini: + LDI r64@0x00000, 99 + END +EOS +"$CPU_AS" -o "$base-fini.o" "$base-fini.s" >/dev/null +"$CPU_LD" -o "$base-fini" --stack-size=1K "$base-fini.o" >/dev/null +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x00000 -- "$base-fini"` +test "x$out" = "x99" + +# A raw stage action is exactly one dump; comma lists are deliberately invalid. +if "$CPU_RUN" --before-run=print-raw-data:1@16,1@17 -- "$base-model" \ + >/dev/null 2>&1; then + echo "raw stage action accepted a comma-separated list" >&2 + exit 1 +fi + +# Arbitrary raw writes are intentionally absent. +if "$CPU_RUN" --before-run=set-raw-input:1@0=00 -- "$base-model" \ + >/dev/null 2>&1; then + echo "set-raw-input unexpectedly accepted" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-static-archive-link-runner.sh b/tests/tst-static-archive-link-runner.sh new file mode 100755 index 0000000..596fa16 --- /dev/null +++ b/tests/tst-static-archive-link-runner.sh @@ -0,0 +1,263 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-static-archive-link-$$ +mkdir -p "$TMP/lib" "$TMP/first" "$TMP/second" "$TMP/order" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main-entry.s" <<'EOS' +.global _life +.extern entry +.text +_start_life: + LDI r64@0x00000, 40 + CALL entry + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/helper.s" <<'EOS' +.global helper +.text +helper: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/entry.s" <<'EOS' +.global entry +.extern helper +.text +entry: + CALL helper + RET +EOS + +cat > "$TMP/unused.s" <<'EOS' +.global unused +.extern missing_forever +.text +unused: + CALL missing_forever + RET +EOS + +"$CPU_AS" -o "$TMP/main-entry.o" "$TMP/main-entry.s" +"$CPU_AS" -o "$TMP/helper.o" "$TMP/helper.s" +"$CPU_AS" -o "$TMP/entry.o" "$TMP/entry.s" +"$CPU_AS" -o "$TMP/unused.o" "$TMP/unused.s" + +# The archive intentionally has no symbol-index member. helper.o precedes +# entry.o: entry.o is extracted first, introduces an undefined helper, and a +# repeated scan must then reach the earlier helper.o. unused.o must never be +# extracted, otherwise its missing_forever reference would break the link. +"$CPU_AR" rc "$TMP/lib/libchain.a" \ + "$TMP/helper.o" "$TMP/entry.o" "$TMP/unused.o" + +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -lchain \ + -o "$TMP/static-chain" "$TMP/main-entry.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/static-chain"`" = 42 ] + +# ABI-v3 also falls back to a static archive when no libchain.so exists. +# Extracted members are part of the output image and therefore add no NEEDED. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lchain -o "$TMP/dynamic-chain" "$TMP/main-entry.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/dynamic-chain"`" = 42 ] +"$CPU_READELF" -d "$TMP/dynamic-chain" > "$TMP/dynamic-chain.dynamic" +[ "`grep -c 'NEEDED' "$TMP/dynamic-chain.dynamic" || true`" -eq 0 ] + +# ABI-v3 ET_DYN output follows the same static-archive fallback rule. The +# selected members become part of the shared object and do not add NEEDED. +cat > "$TMP/wrapper.s" <<'EOS' +.global wrapper +.extern entry +.text +wrapper: + CALL entry + RET +EOS + +"$CPU_AS" -o "$TMP/wrapper.o" "$TMP/wrapper.s" +"$CPU_LD" --shared --soname=libwrapper.so.1 \ + -L"$TMP/lib" -lchain -o "$TMP/libwrapper.so" "$TMP/wrapper.o" +"$CPU_READELF" -d "$TMP/libwrapper.so" > "$TMP/libwrapper.dynamic" +grep -q 'SONAME.*\[libwrapper.so.1\]' "$TMP/libwrapper.dynamic" +[ "`grep -c 'NEEDED' "$TMP/libwrapper.dynamic" || true`" -eq 0 ] + +# A weak undefined symbol does not cause archive extraction. Its PTR64 +# relocation therefore remains the established NULL weak result. +cat > "$TMP/weak-main.s" <<'EOS' +.global _life +.weak optional +.data +optional_ptr: + .ptr optional +.text +_start_life: + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/optional.s" <<'EOS' +.global optional +.text +optional: + RET +EOS + +"$CPU_AS" -o "$TMP/weak-main.o" "$TMP/weak-main.s" +"$CPU_AS" -o "$TMP/optional.o" "$TMP/optional.s" +"$CPU_AR" rcs "$TMP/lib/liboptional.a" "$TMP/optional.o" +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -loptional \ + -o "$TMP/weak-main" "$TMP/weak-main.o" +[ "`"$CPU_RUN" --int-data=64@16 "$TMP/weak-main"`" = 0 ] + +# Static archive order across -l operands follows traditional one-pass library +# semantics. A later archive can satisfy references introduced by an earlier +# one, but an already-scanned archive is not implicitly revisited. +cat > "$TMP/order-main.s" <<'EOS' +.global _life +.extern a1 +.text +_start_life: + LDI r64@0x00000, 40 + CALL a1 + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a1.s" <<'EOS' +.global a1 +.extern b1 +.text +a1: + CALL b1 + RET +EOS + +cat > "$TMP/a2.s" <<'EOS' +.global a2 +.text +a2: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b1.s" <<'EOS' +.global b1 +.extern a2 +.text +b1: + CALL a2 + RET +EOS + +"$CPU_AS" -o "$TMP/order-main.o" "$TMP/order-main.s" +"$CPU_AS" -o "$TMP/a1.o" "$TMP/a1.s" +"$CPU_AS" -o "$TMP/a2.o" "$TMP/a2.s" +"$CPU_AS" -o "$TMP/b1.o" "$TMP/b1.s" +"$CPU_AR" rcs "$TMP/order/liba.a" "$TMP/a1.o" "$TMP/a2.o" +"$CPU_AR" rcs "$TMP/order/libb.a" "$TMP/b1.o" + +if "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ + -la -lb -o "$TMP/order-fail" "$TMP/order-main.o" \ + >/dev/null 2>"$TMP/order-fail.err"; then + echo "previous archive was unexpectedly rescanned across -l operands" >&2 + exit 1 +fi +grep -q 'undefined symbol: a2' "$TMP/order-fail.err" + +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ + -la -lb -la -o "$TMP/order-ok" "$TMP/order-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/order-ok"`" = 42 ] + +# Prepare two providers for -lpick: a static one returns 42, a shared one 43. +cat > "$TMP/pick-main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/pick-static.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/pick-shared.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/pick-main.o" "$TMP/pick-main.s" +"$CPU_AS" -o "$TMP/pick-static.o" "$TMP/pick-static.s" +"$CPU_AS" -o "$TMP/pick-shared.o" "$TMP/pick-shared.s" + +"$CPU_AR" rcs "$TMP/first/libpick.a" "$TMP/pick-static.o" +"$CPU_LD" --shared --soname=libpick.so.7 \ + -o "$TMP/second/libpick.so.7.0.0" "$TMP/pick-shared.o" +ln -s libpick.so.7.0.0 "$TMP/second/libpick.so.7" +ln -s libpick.so.7 "$TMP/second/libpick.so" + +# Search is directory-major. The .a in the first -L directory wins over a +# .so in a later directory. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/first" -L"$TMP/second" -lpick \ + -o "$TMP/pick-first-static" "$TMP/pick-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-first-static"`" = 42 ] +"$CPU_READELF" -d "$TMP/pick-first-static" > "$TMP/pick-first-static.dynamic" +[ "`grep -c 'NEEDED' "$TMP/pick-first-static.dynamic" || true`" -eq 0 ] + +# Reversing -L order selects the shared object and records its SONAME. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/second" -L"$TMP/first" -lpick \ + -o "$TMP/pick-first-shared" "$TMP/pick-main.o" +"$CPU_READELF" -d "$TMP/pick-first-shared" > "$TMP/pick-first-shared.dynamic" +grep -q 'NEEDED.*\[libpick.so.7\]' "$TMP/pick-first-shared.dynamic" +[ "`"$CPU_RUN" --library-path="$TMP/second" \ + --int-reg=r64@0x00018 "$TMP/pick-first-shared"`" = 43 ] + +# Within one directory ABI-v3 prefers .so over .a. +cp "$TMP/first/libpick.a" "$TMP/second/libpick.a" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-same-dir" "$TMP/pick-main.o" +"$CPU_READELF" -d "$TMP/pick-same-dir" | grep -q 'NEEDED.*\[libpick.so.7\]' + +# ABI-v1 never selects .so for -l. It uses the archive even when both exist. +"$CPU_LD" --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-abi1" "$TMP/pick-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-abi1"`" = 42 ] + +# With only a shared provider available, ABI-v1 -l must fail rather than +# silently introducing a dynamic dependency into a static executable. +rm -f "$TMP/second/libpick.a" +if "$CPU_LD" --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-abi1-bad" "$TMP/pick-main.o" \ + >/dev/null 2>&1; then + echo "ABI-v1 unexpectedly selected a shared library" >&2 + exit 1 +fi + +"$CPU_LD" --help 2>&1 | grep -q 'ABI-v1 and -r search .a' +"$CPU_LD" --help 2>&1 | grep -q 'ABI-v3 searches .so then .a' + +exit 0 diff --git a/tests/tst-static-option-runner.sh b/tests/tst-static-option-runner.sh new file mode 100755 index 0000000..1515a7f --- /dev/null +++ b/tests/tst-static-option-runner.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-static-option-$$ +mkdir -p "$TMP/both" "$TMP/soonly" "$TMP/aonly" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/wrapper.s" <<'EOS' +.global wrapper +.extern pick +.text +wrapper: + CALL pick + RET +EOS + +cat > "$TMP/pick-static.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/pick-shared.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/wrapper.o" "$TMP/wrapper.s" +"$CPU_AS" -o "$TMP/pick-static.o" "$TMP/pick-static.s" +"$CPU_AS" -o "$TMP/pick-shared.o" "$TMP/pick-shared.s" + +"$CPU_AR" rcs "$TMP/both/libpick.a" "$TMP/pick-static.o" +cp "$TMP/both/libpick.a" "$TMP/aonly/libpick.a" +"$CPU_LD" --shared --soname=libpick.so.1 \ + -o "$TMP/both/libpick.so.1.0.0" "$TMP/pick-shared.o" +ln -s libpick.so.1.0.0 "$TMP/both/libpick.so.1" +ln -s libpick.so.1 "$TMP/both/libpick.so" +cp "$TMP/both/libpick.so.1.0.0" "$TMP/soonly/libpick.so.1.0.0" +ln -s libpick.so.1.0.0 "$TMP/soonly/libpick.so.1" +ln -s libpick.so.1 "$TMP/soonly/libpick.so" + +# Normal ABI-v3 lookup prefers .so in a directory containing both providers. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/dynamic-shared" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/dynamic-shared" > "$TMP/dynamic-shared.dynamic" +grep -q 'NEEDED.*\[libpick.so.1\]' "$TMP/dynamic-shared.dynamic" +[ "`"$CPU_RUN" --library-path="$TMP/both" \ + --int-reg=r64@0x00018 "$TMP/dynamic-shared"`" = 43 ] + +# --static changes only -l resolution. The output is still ABI-v3 ET_EXEC, +# but libpick.a is selected and no DT_NEEDED is emitted. +"$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/dynamic-static" "$TMP/main.o" +"$CPU_READELF" -h "$TMP/dynamic-static" > "$TMP/dynamic-static.header" +"$CPU_READELF" -d "$TMP/dynamic-static" > "$TMP/dynamic-static.dynamic" +grep -q 'ABI Version:.*3' "$TMP/dynamic-static.header" +[ "`grep -c 'NEEDED' "$TMP/dynamic-static.dynamic" || true`" -eq 0 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/dynamic-static"`" = 42 ] + +# --shared --static remains ABI-v3 ET_DYN with its own SONAME; the selected +# archive member is incorporated into the image and adds no NEEDED entry. +"$CPU_LD" --shared --static --soname=libwrapper.so.1 \ + -L"$TMP/both" -lpick -o "$TMP/libwrapper.so" "$TMP/wrapper.o" +"$CPU_READELF" -d "$TMP/libwrapper.so" > "$TMP/libwrapper.dynamic" +grep -q 'SONAME.*\[libwrapper.so.1\]' "$TMP/libwrapper.dynamic" +[ "`grep -c 'NEEDED' "$TMP/libwrapper.dynamic" || true`" -eq 0 ] + +# -r already accepts static archives only; --static is therefore a harmless +# explicit statement of the same library-selection policy. +"$CPU_LD" -r --static -L"$TMP/both" -lpick \ + -o "$TMP/partial.o" "$TMP/wrapper.o" +"$CPU_READELF" -s "$TMP/partial.o" > "$TMP/partial.sym" +grep -q '[[:space:]]pick$' "$TMP/partial.sym" + +# ABI-v1 is already fully static; accepting --static must preserve that path. +"$CPU_LD" --static --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/abi1-static" "$TMP/main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/abi1-static"`" = 42 ] + +# Forced-static lookup skips a .so-only earlier directory and continues until +# it finds libpick.a in a later directory. +"$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/soonly" -L"$TMP/aonly" -lpick \ + -o "$TMP/later-archive" "$TMP/main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/later-archive"`" = 42 ] +"$CPU_READELF" -d "$TMP/later-archive" > "$TMP/later-archive.dynamic" +[ "`grep -c 'NEEDED' "$TMP/later-archive.dynamic" || true`" -eq 0 ] + +# With only a shared provider available, --static must fail and must not fall +# back to libpick.so. +if "$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/soonly" -lpick -o "$TMP/missing-static" "$TMP/main.o" \ + >/dev/null 2>"$TMP/missing-static.err"; then + echo "--static unexpectedly selected a shared library" >&2 + exit 1 +fi +grep -q 'libpick.a' "$TMP/missing-static.err" + +"$CPU_LD" --help 2>&1 | grep -q -- '--static' +"$CPU_LD" --help 2>&1 | grep -q 'force every -l<name> lookup to use lib<name>.a' + +exit 0 diff --git a/tests/tst-strip-fixture.c b/tests/tst-strip-fixture.c new file mode 100644 index 0000000..1d1fae3 --- /dev/null +++ b/tests/tst-strip-fixture.c @@ -0,0 +1,266 @@ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" + + +typedef struct fixture_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} fixture_shdr; + + +static void +get_shdr( const unsigned char *p, int data, fixture_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +put_shdr( unsigned char *p, int data, const fixture_shdr *sh ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, sh->name, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, sh->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, sh->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, sh->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, sh->offset, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, sh->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, sh->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, sh->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, sh->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, sh->entsize, data ); + +} /* End of put_shdr() */ + + +static const char * +name_at( const unsigned char *table, size_t size, __mpu_uint32_t offset ) +{ + const unsigned char *p; + + if( offset >= size ) + return( NULL ); + p = table + offset; + if( !memchr( p, 0, size - offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of name_at() */ + + +int +main( int argc, char **argv ) +{ + unsigned char *file = NULL; + unsigned char *out = NULL; + fixture_shdr *sh = NULL; + const unsigned char *old_shstr; + size_t file_size = 0; + size_t old_shstr_size; + size_t new_shstr_size; + size_t i; + size_t text_index = 0; + int data; + int overflow = 0; + __mpu_uint64_t old_shoff; + __mpu_uint64_t cur; + __mpu_uint64_t new_shoff; + __mpu_uint64_t total; + __mpu_uint16_t shnum; + __mpu_uint16_t shentsize; + __mpu_uint16_t shstrndx; + fixture_shdr strtab; + fixture_shdr symtab; + fixture_shdr shstr; + unsigned char symbols[2 * MELF64_SYM_SIZE]; + const char fixture_strtab[] = "\0fixture_symbol\0"; + const char strtab_name[] = ".strtab"; + const char symtab_name[] = ".symtab"; + char err[256]; + int rc = EXIT_FAILURE; + + if( argc != 3 ) + { + fprintf( stderr, "usage: %s INPUT-ET_EXEC-OR-ET_DYN OUTPUT\n", argv[0] ); + return( EXIT_FAILURE ); + } + + if( cpu_melf_readall( argv[1], &file, &file_size, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s\n", err ); + return( EXIT_FAILURE ); + } + + if( file_size < MELF64_EHDR_SIZE || + file[MELF_EI_MAG0] != MELFMAG0 || file[MELF_EI_MAG1] != MELFMAG1 || + file[MELF_EI_MAG2] != MELFMAG2 || file[MELF_EI_MAG3] != MELFMAG3 || + file[MELF_EI_CLASS] != MELF_CLASS_64 ) + goto out; + + data = file[MELF_EI_DATA]; + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + goto out; + { + __mpu_uint16_t type = cpu_melf_get16( + file + MELF64_EHDR_E_TYPE_OFF, data ); + + if( type != MELF_ET_EXEC && type != MELF_ET_DYN ) + goto out; + } + + old_shoff = cpu_melf_get64( file + MELF64_EHDR_E_SHOFF_OFF, data ); + shnum = cpu_melf_get16( file + MELF64_EHDR_E_SHNUM_OFF, data ); + shentsize = cpu_melf_get16( file + MELF64_EHDR_E_SHENTSIZE_OFF, data ); + shstrndx = cpu_melf_get16( file + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + if( !shnum || shnum > UINT16_MAX - 2 || shentsize != MELF64_SHDR_SIZE || + shstrndx >= shnum || + !cpu_melf_span( file_size, old_shoff, + (__mpu_uint64_t)shnum * MELF64_SHDR_SIZE ) ) + goto out; + + sh = (fixture_shdr *)calloc( (size_t)shnum + 2, sizeof( *sh ) ); + if( !sh ) + goto out; + for( i = 0; i < shnum; ++i ) + get_shdr( file + (size_t)old_shoff + i * MELF64_SHDR_SIZE, + data, &sh[i] ); + + if( sh[shstrndx].type != MELF_SHT_STRTAB || + !cpu_melf_span( file_size, sh[shstrndx].offset, + sh[shstrndx].size ) || + sh[shstrndx].size > SIZE_MAX ) + goto out; + + old_shstr = file + (size_t)sh[shstrndx].offset; + old_shstr_size = (size_t)sh[shstrndx].size; + for( i = 1; i < shnum; ++i ) + { + const char *name = name_at( old_shstr, old_shstr_size, sh[i].name ); + + if( !name ) + goto out; + if( !strcmp( name, MELF_SEC_TEXT ) ) + text_index = i; + } + if( !text_index ) + goto out; + + cur = old_shoff; + memset( &strtab, 0, sizeof( strtab ) ); + strtab.type = MELF_SHT_STRTAB; + strtab.offset = cur; + strtab.size = sizeof( fixture_strtab ); + strtab.align = 1; + cur += strtab.size; + + cur = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow ) + goto out; + memset( &symtab, 0, sizeof( symtab ) ); + symtab.type = MELF_SHT_SYMTAB; + symtab.offset = cur; + symtab.size = sizeof( symbols ); + symtab.link = shnum; + symtab.info = 1; + symtab.align = 8; + symtab.entsize = MELF64_SYM_SIZE; + cur += symtab.size; + + if( old_shstr_size > SIZE_MAX - sizeof( strtab_name ) || + old_shstr_size + sizeof( strtab_name ) > SIZE_MAX - sizeof( symtab_name ) ) + goto out; + new_shstr_size = old_shstr_size + sizeof( strtab_name ) + + sizeof( symtab_name ); + shstr = sh[shstrndx]; + shstr.offset = cur; + shstr.size = new_shstr_size; + cur += new_shstr_size; + + new_shoff = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow || new_shoff > UINT64_MAX - + (__mpu_uint64_t)( shnum + 2 ) * MELF64_SHDR_SIZE ) + goto out; + total = new_shoff + (__mpu_uint64_t)( shnum + 2 ) * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + goto out; + + out = (unsigned char *)calloc( (size_t)total, 1 ); + if( !out ) + goto out; + memcpy( out, file, (size_t)old_shoff ); + memcpy( out + (size_t)strtab.offset, fixture_strtab, + sizeof( fixture_strtab ) ); + + memset( symbols, 0, sizeof( symbols ) ); + cpu_melf_put32( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_NAME_OFF, + 1, data ); + symbols[MELF64_SYM_SIZE + MELF64_SYM_ST_INFO_OFF] = + MELF64_ST_INFO( MELF_STB_GLOBAL, MELF_STT_FUNC ); + symbols[MELF64_SYM_SIZE + MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_SHNDX_OFF, + (__mpu_uint16_t)text_index, data ); + cpu_melf_put64( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_VALUE_OFF, + sh[text_index].addr, data ); + memcpy( out + (size_t)symtab.offset, symbols, sizeof( symbols ) ); + + memcpy( out + (size_t)shstr.offset, old_shstr, old_shstr_size ); + memcpy( out + (size_t)shstr.offset + old_shstr_size, + strtab_name, sizeof( strtab_name ) ); + memcpy( out + (size_t)shstr.offset + old_shstr_size + sizeof( strtab_name ), + symtab_name, sizeof( symtab_name ) ); + + strtab.name = (__mpu_uint32_t)old_shstr_size; + symtab.name = (__mpu_uint32_t)( old_shstr_size + sizeof( strtab_name ) ); + sh[shstrndx] = shstr; + sh[shnum] = strtab; + sh[shnum + 1] = symtab; + + for( i = 0; i < (size_t)shnum + 2; ++i ) + put_shdr( out + (size_t)new_shoff + i * MELF64_SHDR_SIZE, + data, &sh[i] ); + + cpu_melf_put64( out + MELF64_EHDR_E_SHOFF_OFF, new_shoff, data ); + cpu_melf_put16( out + MELF64_EHDR_E_SHNUM_OFF, + (__mpu_uint16_t)( shnum + 2 ), data ); + + if( cpu_melf_writeall( argv[2], out, (size_t)total, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s\n", err ); + goto out; + } + rc = EXIT_SUCCESS; + +out: + free( out ); + free( sh ); + free( file ); + return( rc ); + +} /* End of main() */ diff --git a/tests/tst-strip-runner.sh b/tests/tst-strip-runner.sh new file mode 100755 index 0000000..6d0005d --- /dev/null +++ b/tests/tst-strip-runner.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${CPU_AR:?}" "${CPU_NM:?}" "${CPU_STRIP:?}" "${STRIP_FIXTURE:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-strip-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.text +_start_life: + LDI r64@0x00000, 42 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + + +cat > "$TMP/dynamic-main.s" <<'EOS' +.global _life +.extern add_one +.text +_start_life: + LDI r64@0x00000, 41 + CALL add_one + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/add-one.s" <<'EOS' +.global add_one +.text +add_one: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/add-one.o" "$TMP/add-one.s" +"$CPU_AS" -o "$TMP/dynamic-main.o" "$TMP/dynamic-main.s" +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/dynamic-main" "$TMP/dynamic-main.o" +"$CPU_LD" --shared --soname=libadd.so.1 -o "$TMP/libadd.so" "$TMP/add-one.o" +"$CPU_AR" rcs "$TMP/libadd.a" "$TMP/add-one.o" + +# ET_REL .symtab/.strtab are link semantics and are preserved byte-for-byte. +"$CPU_STRIP" -o "$TMP/main.stripped.o" "$TMP/main.o" +cmp "$TMP/main.o" "$TMP/main.stripped.o" +"$CPU_READELF" -s "$TMP/main.stripped.o" | grep -q '_life' + +# Current final writers already emit no ordinary .symtab, so these are no-ops. +"$CPU_STRIP" -o "$TMP/main.clean" "$TMP/main" +cmp "$TMP/main" "$TMP/main.clean" +"$CPU_STRIP" -o "$TMP/libadd.clean.so" "$TMP/libadd.so" +cmp "$TMP/libadd.so" "$TMP/libadd.clean.so" +"$CPU_READELF" -S "$TMP/libadd.clean.so" > "$TMP/libadd.sections" +grep -q '\.dynsym' "$TMP/libadd.sections" +grep -q '\.dynstr' "$TMP/libadd.sections" +grep -q '\.mcpu.target' "$TMP/libadd.sections" + +# The same removal policy on ET_DYN must preserve dynamic symbols/relocations. +"$STRIP_FIXTURE" "$TMP/libadd.so" "$TMP/libadd.with-sym.so" +"$CPU_READELF" -S -s "$TMP/libadd.with-sym.so" > "$TMP/dyn-before.txt" +grep -q '\.symtab' "$TMP/dyn-before.txt" +"$CPU_STRIP" -o "$TMP/libadd.really-stripped.so" "$TMP/libadd.with-sym.so" +"$CPU_READELF" -S -s "$TMP/libadd.really-stripped.so" > "$TMP/dyn-after.txt" +if grep -q '\.symtab' "$TMP/dyn-after.txt"; then + echo "ordinary ET_DYN .symtab survived mcpu-strip" >&2 + exit 1 +fi +grep -q '\.dynsym' "$TMP/dyn-after.txt" +grep -q '\.dynstr' "$TMP/dyn-after.txt" +test "`MCPU_LD_PRELOAD= "$CPU_RUN" --ldpreload="$TMP/libadd.really-stripped.so" --int-reg=r64@0x00018 "$TMP/dynamic-main"`" = 42 + +# Deterministic ET_REL archives keep their members and symbol index intact. +"$CPU_STRIP" -o "$TMP/libadd.clean.a" "$TMP/libadd.a" +cmp "$TMP/libadd.a" "$TMP/libadd.clean.a" +"$CPU_NM" -A "$TMP/libadd.clean.a" | grep -q 'add_one' + +# Add a conventional non-runtime .symtab/.strtab to a valid ET_EXEC fixture. +# mcpu-strip must remove them, shrink the file and leave execution untouched. +"$STRIP_FIXTURE" "$TMP/main" "$TMP/main.with-sym" +chmod 755 "$TMP/main.with-sym" +"$CPU_READELF" -S -s "$TMP/main.with-sym" > "$TMP/before.txt" +grep -q '\.symtab' "$TMP/before.txt" +grep -q 'fixture_symbol' "$TMP/before.txt" +before=`wc -c < "$TMP/main.with-sym"` + +"$CPU_STRIP" -o "$TMP/main.really-stripped" "$TMP/main.with-sym" +"$CPU_READELF" -S -s "$TMP/main.really-stripped" > "$TMP/after.txt" +if grep -q '\.symtab' "$TMP/after.txt"; then + echo "ordinary .symtab survived mcpu-strip" >&2 + exit 1 +fi +if grep -q 'fixture_symbol' "$TMP/after.txt"; then + echo "ordinary symbol string survived mcpu-strip" >&2 + exit 1 +fi +after=`wc -c < "$TMP/main.really-stripped"` +test "$after" -lt "$before" +test "`stat -c %a "$TMP/main.really-stripped"`" = 755 +test "`"$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.really-stripped"`" = 42 + +# In-place replacement is atomic from the caller's point of view and keeps mode. +cp "$TMP/main.with-sym" "$TMP/main.inplace" +chmod 751 "$TMP/main.inplace" +"$CPU_STRIP" "$TMP/main.inplace" +test "`stat -c %a "$TMP/main.inplace"`" = 751 +test "`"$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.inplace"`" = 42 +if "$CPU_READELF" -S "$TMP/main.inplace" | grep -q '\.symtab'; then + echo "in-place strip left .symtab" >&2 + exit 1 +fi + +"$CPU_STRIP" --help | grep -q -- '--output=FILE' +"$CPU_STRIP" --version | grep -Fq "$PACKAGE_VERSION" + +if "$CPU_STRIP" "$TMP/main" "$TMP/main.clean" >/dev/null 2>&1; then + echo "mcpu-strip unexpectedly accepted multiple input files" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-symbol-scope-runner.sh b/tests/tst-symbol-scope-runner.sh new file mode 100755 index 0000000..8a4154e --- /dev/null +++ b/tests/tst-symbol-scope-runner.sh @@ -0,0 +1,276 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-symbol-scope-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main-import.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main-import.o" "$TMP/main-import.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1.0.0" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/lib/libb.so.1.0.0" "$TMP/b.o" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" +ln -s libb.so.1.0.0 "$TMP/lib/libb.so.1" +ln -s libb.so.1 "$TMP/lib/libb.so" + +# Normal DT_NEEDED scope is breadth-first and left-to-right. For equal +# GLOBAL definitions the first direct dependency wins. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -lb -o "$TMP/main-ab" "$TMP/main-import.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-ab"`" = 42 ] + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lb -la -o "$TMP/main-ba" "$TMP/main-import.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-ba"`" = 43 ] + +# A main-executable GLOBAL outranks normal dependencies. +cat > "$TMP/main-own.s" <<'EOS' +.global _life +.global foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/main-own.o" "$TMP/main-own.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -o "$TMP/main-own" "$TMP/main-own.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-own"`" = 41 ] + +# Explicit preload is the intentional override scope. Equal-strength GLOBAL +# preloads keep the historical rightmost-higher-priority rule. +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=liba.so:libb.so \ + --int-reg=r64@0x00018 "$TMP/main-own"`" = 43 ] +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so:liba.so \ + --int-reg=r64@0x00018 "$TMP/main-own"`" = 42 ] + +# Binding strength outranks scope class: a WEAK preload must not preempt a +# normal GLOBAL definition. +cat > "$TMP/weak.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 9 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/weak.o" "$TMP/weak.s" +"$CPU_LD" --shared --soname=libweak.so.1 -o "$TMP/libweak.so" "$TMP/weak.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --ldpreload="$TMP/libweak.so" \ + --int-reg=r64@0x00018 "$TMP/main-ab"`" = 42 ] + +# If only WEAK definitions exist, the same scope ordering applies: first normal +# dependency wins, while rightmost explicit preload wins. +cat > "$TMP/weak-main.s" <<'EOS' +.global _life +.weak foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/wa.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/wb.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/weak-main.o" "$TMP/weak-main.s" +"$CPU_AS" -o "$TMP/wa.o" "$TMP/wa.s" +"$CPU_AS" -o "$TMP/wb.o" "$TMP/wb.s" +"$CPU_LD" --shared --soname=libwa.so.1 -o "$TMP/lib/libwa.so.1" "$TMP/wa.o" +"$CPU_LD" --shared --soname=libwb.so.1 -o "$TMP/lib/libwb.so.1" "$TMP/wb.o" +ln -s libwa.so.1 "$TMP/lib/libwa.so" +ln -s libwb.so.1 "$TMP/lib/libwb.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lwa -lwb -o "$TMP/weak-main" "$TMP/weak-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/weak-main"`" = 42 ] +[ "`"$CPU_RUN" --ldpreload="$TMP/lib/libwa.so.1:$TMP/lib/libwb.so.1" \ + --int-reg=r64@0x00018 "$TMP/weak-main"`" = 43 ] + +# A direct dependency outranks a conflicting transitive dependency. The +# resolver must therefore build a true breadth-first scope rather than expose +# DFS traversal order to symbol resolution. +cat > "$TMP/deep.s" <<'EOS' +.global deep_anchor +.global foo +.text +deep_anchor: + RET +foo: + LDI r64@0x00008, 9 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/mid.s" <<'EOS' +.global mid_anchor +.extern deep_anchor +.text +mid_anchor: + CALL deep_anchor + RET +EOS +cat > "$TMP/direct.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/bfs-main.s" <<'EOS' +.global _life +.extern mid_anchor +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL mid_anchor + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/deep.o" "$TMP/deep.s" +"$CPU_AS" -o "$TMP/mid.o" "$TMP/mid.s" +"$CPU_AS" -o "$TMP/direct.o" "$TMP/direct.s" +"$CPU_AS" -o "$TMP/bfs-main.o" "$TMP/bfs-main.s" +"$CPU_LD" --shared --soname=libdeep.so.1 -o "$TMP/lib/libdeep.so.1.0.0" "$TMP/deep.o" +ln -s libdeep.so.1.0.0 "$TMP/lib/libdeep.so.1" +ln -s libdeep.so.1 "$TMP/lib/libdeep.so" +"$CPU_LD" --shared --soname=libmid.so.1 -L"$TMP/lib" -ldeep \ + -o "$TMP/lib/libmid.so.1.0.0" "$TMP/mid.o" +ln -s libmid.so.1.0.0 "$TMP/lib/libmid.so.1" +ln -s libmid.so.1 "$TMP/lib/libmid.so" +"$CPU_LD" --shared --soname=libdirect.so.1 -o "$TMP/lib/libdirect.so.1.0.0" "$TMP/direct.o" +ln -s libdirect.so.1.0.0 "$TMP/lib/libdirect.so.1" +ln -s libdirect.so.1 "$TMP/lib/libdirect.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lmid -ldirect -o "$TMP/bfs-main" "$TMP/bfs-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/bfs-main"`" = 43 ] + +# Diamond graph: A and B both depend on C. SONAME deduplication loads C once, +# while the direct A/B scope still follows left-to-right order. +cat > "$TMP/common.s" <<'EOS' +.global common_anchor +.text +common_anchor: + RET +EOS +cat > "$TMP/da.s" <<'EOS' +.global a_anchor +.global foo +.extern common_anchor +.text +a_anchor: + CALL common_anchor + RET +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/db.s" <<'EOS' +.global b_anchor +.global foo +.extern common_anchor +.text +b_anchor: + CALL common_anchor + RET +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/diamond-main.s" <<'EOS' +.global _life +.extern a_anchor +.extern b_anchor +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL a_anchor + CALL b_anchor + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/common.o" "$TMP/common.s" +"$CPU_AS" -o "$TMP/da.o" "$TMP/da.s" +"$CPU_AS" -o "$TMP/db.o" "$TMP/db.s" +"$CPU_AS" -o "$TMP/diamond-main.o" "$TMP/diamond-main.s" +"$CPU_LD" --shared --soname=libcommon.so.1 -o "$TMP/lib/libcommon.so.1.0.0" "$TMP/common.o" +ln -s libcommon.so.1.0.0 "$TMP/lib/libcommon.so.1" +ln -s libcommon.so.1 "$TMP/lib/libcommon.so" +"$CPU_LD" --shared --soname=libda.so.1 -L"$TMP/lib" -lcommon \ + -o "$TMP/lib/libda.so.1.0.0" "$TMP/da.o" +ln -s libda.so.1.0.0 "$TMP/lib/libda.so.1" +ln -s libda.so.1 "$TMP/lib/libda.so" +"$CPU_LD" --shared --soname=libdb.so.1 -L"$TMP/lib" -lcommon \ + -o "$TMP/lib/libdb.so.1.0.0" "$TMP/db.o" +ln -s libdb.so.1.0.0 "$TMP/lib/libdb.so.1" +ln -s libdb.so.1 "$TMP/lib/libdb.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lda -ldb -o "$TMP/diamond-main" "$TMP/diamond-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/diamond-main"`" = 42 ] + +exit 0 diff --git a/tests/tst-syscall.c b/tests/tst-syscall.c new file mode 100644 index 0000000..92c3fd3 --- /dev/null +++ b/tests/tst-syscall.c @@ -0,0 +1,229 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + +#include "mcpu-asm.h" + + +#define TEST_NR_brk 10 + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +static __mpu_uint64_t +reg64( __mpu_uint32_t address ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); +} + + +static void +put32( __mpu_uint32_t address, __mpu_uint32_t value ) +{ + __cpu_data_put_u32( __cpu_reg_ptr( 32, address ), value ); +} + + +static void +put64( __mpu_uint32_t address, __mpu_uint64_t value ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), value ); +} + + +static int +run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno, + __mpu_uint64_t arg0, __mpu_uint64_t *ret ) +{ + __mpu_uint64_t keep1 = __mpu_UINT64_C( 0x1111111111111111 ); + __mpu_uint64_t keep2 = __mpu_UINT64_C( 0x2222222222222222 ); + __mpu_uint64_t keep3 = __mpu_UINT64_C( 0x3333333333333333 ); + __mpu_uint64_t keep4 = __mpu_UINT64_C( 0x4444444444444444 ); + __mpu_uint64_t keep5 = __mpu_UINT64_C( 0x5555555555555555 ); + int rc; + + put32( CPU_SYSCALL_NARGS_ADDR, nargs ); + put32( CPU_SYSCALL_SYSNO_ADDR, sysno ); + put64( CPU_SYSCALL_ARG0_ADDR, arg0 ); + put64( CPU_SYSCALL_ARG1_ADDR, keep1 ); + put64( CPU_SYSCALL_ARG2_ADDR, keep2 ); + put64( CPU_SYSCALL_ARG3_ADDR, keep3 ); + put64( CPU_SYSCALL_ARG4_ADDR, keep4 ); + put64( CPU_SYSCALL_ARG5_ADDR, keep5 ); + + __cpu_pc = 0; + rc = __cpu_run(); + if( rc != CPU_EXEC_SUCCESS ) return( rc ); + + *ret = reg64( CPU_SYSCALL_RET_ADDR ); + + if( reg64( CPU_SYSCALL_ARG0_ADDR ) != arg0 || + reg64( CPU_SYSCALL_ARG1_ADDR ) != keep1 || + reg64( CPU_SYSCALL_ARG2_ADDR ) != keep2 || + reg64( CPU_SYSCALL_ARG3_ADDR ) != keep3 || + reg64( CPU_SYSCALL_ARG4_ADDR ) != keep4 || + reg64( CPU_SYSCALL_ARG5_ADDR ) != keep5 ) + return( -1000 ); + + return( 0 ); +} + + +static void +regop( cpu_operand *o, unsigned int bits, __mpu_uint32_t address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = address; +} + + +int +main( void ) +{ + static const char source[] = + ".text\n" + "SYSCALL\n" + "HALT\n"; + cpu_asm_image image; + cpu_instruction store; + char err[256]; + __mpu_uint64_t ptr, ret; + __mpu_int64_t sret; + size_t i; + + memset( &image, 0, sizeof( image ) ); + + __cpu_init(); + + CHECK( CPU_SYSCALL_NARGS_ADDR == CPU_SYSCALL_RET_ADDR, + "NARGS overlaps low-address half of RET storage" ); + CHECK( CPU_SYSCALL_SYSNO_ADDR == CPU_SYSCALL_RET_ADDR + 4, + "SYSNO overlaps second half of RET storage" ); + CHECK( CPU_SYSCALL_ARG0_ADDR + 8 == CPU_SYSCALL_RET_ADDR, + "ARG0 is immediately below RET" ); + + CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err ); + CHECK( image.code_size == 4, "SYSCALL/HALT code size" ); + CHECK( image.code[0] == 0x00 && image.code[1] == 0x04, + "SYSCALL opcode encoding" ); + CHECK( __cpu_program_set( image.code, image.code_size ) == 0, + "program load" ); + CHECK( __cpu_heap_alloc( 64, 8192 ) == 0, "heap allocation" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0, + "make initial HEAP pointer" ); + put64( 0x00000, ptr ); + put64( 0x00008, __mpu_UINT64_C( 0xa5a5a5a5a5a5a5a5 ) ); + memset( &store, 0, sizeof( store ) ); + store.opcode = CPU_OP_STOREP; + regop( &store.operand[0], 64, 0x00000 ); + regop( &store.operand[1], 64, 0x00008 ); + CHECK( __cpu_exec( &store ) == CPU_EXEC_MEMORY_FAULT, + "initial break protects HEAP above _end" ); + + CHECK( run_syscall( 1, TEST_NR_brk, CPU_NULL_PTR, &ret ) == 0, + "SYS_brk query" ); + CHECK( __cpu_ptr_segment( ret ) == CPU_PTR_SEG_HEAP && + __cpu_ptr_offset( ret ) == CPU_HEAP_BEGIN, + "initial SYS_brk is HEAP:16" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN + 8 ) == 0, + "make grown break" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk grow inside initial heap size" ); + CHECK( ret == ptr, "SYS_brk grow return" ); + CHECK( __cpu_exec( &store ) == CPU_EXEC_SUCCESS, + "HEAP becomes writable below new break" ); + + memset( __cpu_heap_ptr() + CPU_HEAP_BEGIN, 0x5a, 8 ); + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0, + "make shrink break" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk shrink to _end" ); + CHECK( ret == ptr, "SYS_brk shrink return" ); + for( i = 0; i < 8; ++i ) + CHECK( __cpu_heap_ptr()[CPU_HEAP_BEGIN + i] == 0, + "shrink zeroes released HEAP" ); + CHECK( __cpu_heap_size() == 64, + "shrink does not reduce logical backing size" ); + CHECK( __cpu_exec( &store ) == CPU_EXEC_MEMORY_FAULT, + "shrunk HEAP is inaccessible above _end" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN + 8 ) == 0, + "make regrown break" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk regrow inside retained backing" ); + CHECK( ret == ptr, "SYS_brk regrow return" ); + for( i = 0; i < 8; ++i ) + CHECK( __cpu_heap_ptr()[CPU_HEAP_BEGIN + i] == 0, + "regrown released HEAP remains zero" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 1000 ) == 0, + "make break inside host capacity" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk extends logical heap" ); + CHECK( ret == ptr && __cpu_heap_size() == 1000, + "logical heap extension" ); + __cpu_heap_ptr()[900] = 0x6b; + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 5000 ) == 0, + "make break requiring backing growth" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk grows host backing" ); + CHECK( ret == ptr && __cpu_heap_size() == 5000, + "host-backed heap extension" ); + CHECK( __cpu_heap_ptr()[900] == 0x6b, + "heap relocation preserves old contents" ); + CHECK( __cpu_heap_ptr()[4999] == 0, + "newly allocated heap memory is zero" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 8193 ) == 0, + "make break above immutable limit" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk above limit returns normally" ); + CHECK( __cpu_ptr_segment( ret ) == CPU_PTR_SEG_HEAP && + __cpu_ptr_offset( ret ) == 5000 && __cpu_heap_size() == 5000, + "heap limit rejection preserves current break" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, 32 ) == 0, + "make non-HEAP pointer" ); + CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk rejects non-HEAP pointer" ); + CHECK( __cpu_ptr_offset( ret ) == 5000, + "invalid SYS_brk returns current break" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 6000 ) == 0, + "make break for bad NARGS" ); + CHECK( run_syscall( 2, TEST_NR_brk, ptr, &ret ) == 0, + "SYS_brk bad NARGS" ); + CHECK( __cpu_ptr_offset( ret ) == 5000, + "bad SYS_brk NARGS returns current break" ); + + CHECK( run_syscall( 0, 9, 0, &ret ) == 0, + "unimplemented syscall execution" ); + memcpy( &sret, &ret, sizeof( sret ) ); + CHECK( sret == -(__mpu_int64_t)ENOSYS, + "unimplemented syscall returns -ENOSYS" ); + + cpu_asm_image_free( &image ); + __cpu_free_context(); + + puts( "libmcpu SYSCALL/SYS_brk regression: PASS" ); + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-tool-config-runner.sh b/tests/tst-tool-config-runner.sh new file mode 100755 index 0000000..790da58 --- /dev/null +++ b/tests/tst-tool-config-runner.sh @@ -0,0 +1,97 @@ +#!/bin/sh +set -eu + +: "${TOOL_CONFIG_TEST:?}" "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-tool-config-$$ +mkdir -p "$TMP/home/.mcpu/etc" "$TMP/relative-root/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +HOME="$TMP/home" +export HOME + +# Baseline: no config files, executable-derived root remains active. +"$TOOL_CONFIG_TEST" --no-config > "$TMP/default.txt" +DEFAULT_LIB=$(sed -n 's/^lib=//p' "$TMP/default.txt") +test -n "$DEFAULT_LIB" + +# The obsolete per-user path must be ignored. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = /obsolete/path;' \ + > "$HOME/.mcpu/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" > "$TMP/old-user.txt" +test "$(sed -n 's/^lib=//p' "$TMP/old-user.txt")" = "$DEFAULT_LIB" + +# The new per-user path has highest user-level priority and expands $HOME. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<'EOC' +MCPU_LD_SYSTEM_LIBRARY_PATH = $HOME/mcpu-next/lib; +EOC +"$TOOL_CONFIG_TEST" > "$TMP/user.txt" +test "$(sed -n 's/^lib=//p' "$TMP/user.txt")" = "$HOME/mcpu-next/lib" + +# An explicit empty value disables the configured system library tree. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" > "$TMP/empty.txt" +test -z "$(sed -n 's/^lib=//p' "$TMP/empty.txt")" + +# --no-config ignores even a malformed user configuration and keeps default. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = broken' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" --no-config > "$TMP/no-config.txt" +test "$(sed -n 's/^lib=//p' "$TMP/no-config.txt")" = "$DEFAULT_LIB" +if "$TOOL_CONFIG_TEST" > /dev/null 2>&1; then + echo "malformed user configuration was unexpectedly accepted" >&2 + exit 1 +fi + +# Absolute --sys-root implies --no-config and uses PATH/lib. +"$TOOL_CONFIG_TEST" --sys-root="$TMP/absolute-root" > "$TMP/absolute.txt" +test "$(sed -n 's/^root=//p' "$TMP/absolute.txt")" = "$TMP/absolute-root" +test "$(sed -n 's/^lib=//p' "$TMP/absolute.txt")" = "$TMP/absolute-root/lib" + +# Relative --sys-root is resolved from the invocation working directory. +( + cd "$TMP" + "$TOOL_CONFIG_TEST" --sys-root=relative-root > "$TMP/relative.txt" +) +test "$(sed -n 's/^root=//p' "$TMP/relative.txt")" = "$TMP/relative-root" +test "$(sed -n 's/^lib=//p' "$TMP/relative.txt")" = "$TMP/relative-root/lib" + + +# The production linker and runner use the same policy state. +"$CPU_AS" -o "$TMP/radd.o" "$TEST_SRCDIR/fixtures/radd-pi.s" >/dev/null +if "$CPU_LD" --stack-size=512 -o "$TMP/radd.melf" "$TMP/radd.o" >/dev/null 2>&1; then + echo "mcpu-ld unexpectedly accepted malformed user configuration" >&2 + exit 1 +fi +"$CPU_LD" --no-config --stack-size=512 \ + -o "$TMP/radd.melf" "$TMP/radd.o" >/dev/null +"$CPU_RUN" --no-config --digits=60 --real-data=4096@16 \ + "$TMP/radd.melf" > "$TMP/run-no-config.txt" +case "$(cat "$TMP/run-no-config.txt")" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected --no-config execution result" >&2; exit 1 ;; +esac + +if "$CPU_RUN" --digits=60 --real-data=4096@16 \ + "$TMP/radd.melf" >/dev/null 2>&1; then + echo "mcpu-run unexpectedly accepted malformed user configuration" >&2 + exit 1 +fi + +"$CPU_LD" --sys-root "$TMP/absolute-root" --stack-size=512 \ + -o "$TMP/radd-sysroot.melf" "$TMP/radd.o" >/dev/null +"$CPU_RUN" --sys-root="$TMP/absolute-root" --digits=60 \ + --real-data=4096@16 "$TMP/radd-sysroot.melf" > "$TMP/run-sysroot.txt" +case "$(cat "$TMP/run-sysroot.txt")" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected --sys-root execution result" >&2; exit 1 ;; +esac + +# Both production tools expose the common policy options. +"$CPU_LD" --help 2>&1 | grep -q -- '--no-config' +"$CPU_LD" --help 2>&1 | grep -q -- '--sys-root=PATH' +"$CPU_RUN" --help 2>&1 | grep -q -- '--no-config' +"$CPU_RUN" --help 2>&1 | grep -q -- '--sys-root=PATH' + +exit 0 diff --git a/tests/tst-tool-config.c b/tests/tst-tool-config.c new file mode 100644 index 0000000..8ea0ec6 --- /dev/null +++ b/tests/tst-tool-config.c @@ -0,0 +1,45 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include "mcpu-tool-options.h" + +int +main( int argc, char **argv ) +{ + mcpu_tool_options options; + int i; + int rc = 1; + + mcpu_tool_options_init( &options, argv[0] ); + + for( i = 1; i < argc; ++i ) + { + int parsed = mcpu_tool_parse_common_option( &options, argc, argv, &i ); + + if( parsed < 0 ) + goto done; + if( parsed == 0 ) + { + fprintf( stderr, "%s: unknown option '%s'\n", + options.progname, argv[i] ); + goto done; + } + } + + if( mcpu_tool_options_prepare(&options, argv[0]) != 0 ) + goto done; + + printf( "root=%s\n", options.runtime.installation_root ); + printf( "config=%s\n", options.runtime.config_file ); + printf( "lib=%s\n", options.runtime.system_library_path ); + rc = 0; + +done: + mcpu_tool_options_free( &options ); + return( rc ); + +} /* End of main() */ diff --git a/tests/tst-tool-inspection-runner.sh b/tests/tst-tool-inspection-runner.sh new file mode 100755 index 0000000..fa34715 --- /dev/null +++ b/tests/tst-tool-inspection-runner.sh @@ -0,0 +1,80 @@ +#!/bin/sh +set -eu + +: "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-tool-inspection-$$ +mkdir -p "$TMP/home/.mcpu/etc" "$TMP/root/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +HOME="$TMP/home" +export HOME + +# --sys-root is self-contained, suppresses config files and needs no input. +"$CPU_LD" --sys-root="$TMP/root" --dump-config > "$TMP/ld-sysroot-config.txt" +"$CPU_RUN" --sys-root="$TMP/root" --dump-config > "$TMP/run-sysroot-config.txt" +cmp "$TMP/ld-sysroot-config.txt" "$TMP/run-sysroot-config.txt" +grep '^ runtime root: '"$TMP/root"'$' "$TMP/ld-sysroot-config.txt" >/dev/null +grep '^ configuration: disabled by --sys-root$' "$TMP/ld-sysroot-config.txt" >/dev/null +grep '^MCPU_LD_SYSTEM_LIBRARY_PATH = '"$TMP/root"'/lib;$' "$TMP/ld-sysroot-config.txt" >/dev/null + +cat > "$TMP/search.expected" <<EOT +libraries: + $TMP/root/lib +EOT +"$CPU_LD" --sys-root="$TMP/root" --print-search-dirs > "$TMP/ld-search.txt" +"$CPU_RUN" --sys-root="$TMP/root" --print-search-dirs > "$TMP/run-search.txt" +cmp "$TMP/search.expected" "$TMP/ld-search.txt" +cmp "$TMP/search.expected" "$TMP/run-search.txt" + +# The effective user configuration is visible and is identical in both tools. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/user/lib; +EOC +"$CPU_LD" --dump-config > "$TMP/ld-user-config.txt" +"$CPU_RUN" --dump-config > "$TMP/run-user-config.txt" +cmp "$TMP/ld-user-config.txt" "$TMP/run-user-config.txt" +grep '^ config: '"$HOME/.mcpu/etc/mcpu-ld.conf"' \[read\]$' "$TMP/ld-user-config.txt" >/dev/null +grep '^MCPU_LD_SYSTEM_LIBRARY_PATH = '"$TMP/user/lib"';$' "$TMP/ld-user-config.txt" >/dev/null + +cat > "$TMP/user-search.expected" <<EOT +libraries: + $TMP/user/lib +EOT +"$CPU_LD" --print-search-dirs > "$TMP/ld-user-search.txt" +"$CPU_RUN" --print-search-dirs > "$TMP/run-user-search.txt" +cmp "$TMP/user-search.expected" "$TMP/ld-user-search.txt" +cmp "$TMP/user-search.expected" "$TMP/run-user-search.txt" + +# Empty effective system path means an empty search list; there is no fallback. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +printf '%s\n' 'libraries:' > "$TMP/empty.expected" +"$CPU_LD" --print-search-dirs > "$TMP/ld-empty.txt" +"$CPU_RUN" --print-search-dirs > "$TMP/run-empty.txt" +cmp "$TMP/empty.expected" "$TMP/ld-empty.txt" +cmp "$TMP/empty.expected" "$TMP/run-empty.txt" + +# A malformed config breaks normal inspection, but explicit suppression works. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = broken' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +if "$CPU_LD" --dump-config >/dev/null 2>&1; then + echo "mcpu-ld --dump-config unexpectedly accepted malformed configuration" >&2 + exit 1 +fi +if "$CPU_RUN" --print-search-dirs >/dev/null 2>&1; then + echo "mcpu-run --print-search-dirs unexpectedly accepted malformed configuration" >&2 + exit 1 +fi + +"$CPU_LD" --no-config --dump-config > "$TMP/no-config.txt" +grep '^ configuration: disabled by --no-config$' "$TMP/no-config.txt" >/dev/null +"$CPU_RUN" --sys-root="$TMP/root" --print-search-dirs > /dev/null + +# Both public help texts expose the same diagnostic actions. +"$CPU_LD" --help 2>&1 | grep -q -- '--dump-config' +"$CPU_LD" --help 2>&1 | grep -q -- '--print-search-dirs' +"$CPU_RUN" --help 2>&1 | grep -q -- '--dump-config' +"$CPU_RUN" --help 2>&1 | grep -q -- '--print-search-dirs' + +exit 0 diff --git a/tests/tst-verbose-runner.sh b/tests/tst-verbose-runner.sh new file mode 100755 index 0000000..e1362e4 --- /dev/null +++ b/tests/tst-verbose-runner.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-verbose-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +src="$TEST_SRCDIR/fixtures/radd-pi.s" + +# Production default is quiet: successful assembly and linking write no +# developer summary unless -v/--verbose is requested. +out=`"$CPU_AS" -o "$TMP/main.o" "$src"` +[ -z "$out" ] + +out=`"$CPU_LD" --stack-size=512 --no-heap -o "$TMP/main" "$TMP/main.o"` +[ -z "$out" ] + +# Both spellings enable the existing developer summaries without changing +# their content or the generated object/executable semantics. +out=`"$CPU_AS" -v -o "$TMP/main-v.o" "$src"` +case "$out" in + "MELF ET_REL: "*" -> $TMP/main-v.o") ;; + *) echo "unexpected mcpu-as -v output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_AS" --verbose -o "$TMP/main-long.o" "$src"` +case "$out" in + "MELF ET_REL: "*" -> $TMP/main-long.o") ;; + *) echo "unexpected mcpu-as --verbose output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" -v --stack-size=512 --no-heap \ + -o "$TMP/main-v" "$TMP/main-v.o"` +case "$out" in + "MELF ABI1 ET_EXEC: "*" -> $TMP/main-v") ;; + *) echo "unexpected mcpu-ld -v output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" --verbose --dynamic --stack-size=512 --no-heap \ + -o "$TMP/main-dyn" "$TMP/main-long.o"` +case "$out" in + "MELF ABI3 ET_EXEC: "*" -> $TMP/main-dyn") ;; + *) echo "unexpected mcpu-ld --verbose dynamic output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" -v -r -o "$TMP/main-partial.o" "$TMP/main-v.o"` +case "$out" in + "MELF ET_REL partial link: "*" -> $TMP/main-partial.o") ;; + *) echo "unexpected mcpu-ld -v -r output: $out" >&2; exit 1 ;; +esac + +cat > "$TMP/shared.s" <<'EOS' +.global shared_value +.text +shared_value: + RET +EOS +"$CPU_AS" -o "$TMP/shared.o" "$TMP/shared.s" +out=`"$CPU_LD" --verbose --shared --soname=libverbose.so.1 \ + -o "$TMP/libverbose.so" "$TMP/shared.o"` +case "$out" in + "MELF ABI3 ET_DYN: "*" -> $TMP/libverbose.so") ;; + *) echo "unexpected mcpu-ld --verbose shared output: $out" >&2; exit 1 ;; +esac + +exit 0 diff --git a/utils/Makefile.am b/utils/Makefile.am new file mode 100644 index 0000000..e59aff9 --- /dev/null +++ b/utils/Makefile.am @@ -0,0 +1,72 @@ + +AM_CPPFLAGS = -I$(top_srcdir)/include $(LIBMPU_CPPFLAGS) $(LIBMPUIO_CPPFLAGS) +AM_CFLAGS = $(LIBMPU_CFLAGS) $(LIBMPUIO_CFLAGS) +AM_LDFLAGS = $(LIBMPU_LDFLAGS) $(LIBMPUIO_LDFLAGS) + +noinst_LTLIBRARIES = libmcpu-version.la +libmcpu_version_la_SOURCES = mcpu-version.c mcpu-version.h + +pkglibexecdir = @MCPU_BINDIR@ +pkglibexec_PROGRAMS = mcpu-as mcpu-run mcpu-ld mcpu-readelf mcpu-ar mcpu-nm mcpu-objdump mcpu-strip mcpu-objcopy + +mcpu_as_SOURCES = mcpu-as.c mcpu-asm.c mcpu-asm.h mcpu-object.c mcpu-object.h mcpu-melf.c mcpu-melf.h +mcpu_as_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_ld_SOURCES = mcpu-ld.c mcpu-ld-options.c mcpu-ld-options.h mcpu-tool-options.c mcpu-tool-options.h mcpu-library-search.c mcpu-library-search.h mcpu-runtime.c mcpu-runtime.h mcpu-config.c mcpu-config.h mcpu-archive.c mcpu-archive.h mcpu-asm.c mcpu-asm.h mcpu-object.c mcpu-object.h mcpu-link.c mcpu-link.h mcpu-exec.c mcpu-exec.h mcpu-dyn.c mcpu-dyn.h mcpu-melf.c mcpu-melf.h +mcpu_ld_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_run_SOURCES = mcpu-run.c mcpu-run-options.c mcpu-run-options.h mcpu-dependency.c mcpu-dependency.h mcpu-tool-options.c mcpu-tool-options.h mcpu-library-search.c mcpu-library-search.h mcpu-runtime.c mcpu-runtime.h mcpu-config.c mcpu-config.h mcpu-exec.c mcpu-exec.h mcpu-dyn.c mcpu-dyn.h mcpu-melf.c mcpu-melf.h +mcpu_run_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_readelf_SOURCES = mcpu-readelf.c mcpu-melf.c mcpu-melf.h +mcpu_readelf_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_ar_SOURCES = mcpu-ar.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_ar_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_nm_SOURCES = mcpu-nm.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_nm_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_objdump_SOURCES = mcpu-objdump.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_objdump_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_strip_SOURCES = mcpu-strip.c mcpu-object-edit.c mcpu-object-edit.h mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_strip_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_objcopy_SOURCES = mcpu-objcopy.c mcpu-object-edit.c mcpu-object-edit.h mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_objcopy_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +install-exec-hook: + $(MKDIR_P) "$(DESTDIR)$(bindir)" + rm -f "$(DESTDIR)$(bindir)/mcpu-as" + rm -f "$(DESTDIR)$(bindir)/mcpu-ld" + rm -f "$(DESTDIR)$(bindir)/mcpu-run" + rm -f "$(DESTDIR)$(bindir)/mcpu-readelf" + rm -f "$(DESTDIR)$(bindir)/mcpu-ar" + rm -f "$(DESTDIR)$(bindir)/mcpu-nm" + rm -f "$(DESTDIR)$(bindir)/mcpu-objdump" + rm -f "$(DESTDIR)$(bindir)/mcpu-strip" + rm -f "$(DESTDIR)$(bindir)/mcpu-objcopy" + $(LN_S) "@MCPU_AS_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-as" + $(LN_S) "@MCPU_LD_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-ld" + $(LN_S) "@MCPU_RUN_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-run" + $(LN_S) "@MCPU_READELF_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-readelf" + $(LN_S) "@MCPU_AR_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-ar" + $(LN_S) "@MCPU_NM_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-nm" + $(LN_S) "@MCPU_OBJDUMP_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-objdump" + $(LN_S) "@MCPU_STRIP_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-strip" + $(LN_S) "@MCPU_OBJCOPY_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-objcopy" + +uninstall-hook: + rm -f "$(DESTDIR)$(bindir)/mcpu-as" + rm -f "$(DESTDIR)$(bindir)/mcpu-ld" + rm -f "$(DESTDIR)$(bindir)/mcpu-run" + rm -f "$(DESTDIR)$(bindir)/mcpu-readelf" + rm -f "$(DESTDIR)$(bindir)/mcpu-ar" + rm -f "$(DESTDIR)$(bindir)/mcpu-nm" + rm -f "$(DESTDIR)$(bindir)/mcpu-objdump" + rm -f "$(DESTDIR)$(bindir)/mcpu-strip" + rm -f "$(DESTDIR)$(bindir)/mcpu-objcopy" + +distclean-local: + -rm -rf $(DEPDIR) diff --git a/utils/mcpu-ar.c b/utils/mcpu-ar.c new file mode 100644 index 0000000..c3fe4bf --- /dev/null +++ b/utils/mcpu-ar.c @@ -0,0 +1,542 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-archive.h" +#include "mcpu-version.h" + + +typedef struct mcpu_ar_options +{ + char operation; + int create; + int index; + int verbose; +} mcpu_ar_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] operation archive [member ...]\n\n" + "Create and maintain deterministic UNIX archives of MCPU MELF ET_REL objects.\n\n" + "Operations:\n" + " r replace existing members or append new members\n" + " q append members without replacement\n" + " d delete named members\n" + " t list archive members\n" + " p print member contents to standard output\n" + " x extract members into the current directory\n" + " s rebuild the archive symbol index\n\n" + "Modifiers:\n" + " c accepted for ar compatibility; archive creation is quiet\n" + " s write/rebuild the standard archive symbol index\n" + " v verbose operation\n\n" + "The operation and modifiers may be combined, for example: rcs, tv, xv.\n" + "Member order is preserved. r replaces a matching member in place and\n" + "appends a member that is not already present. q always appends.\n\n" + "Options:\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +parse_operation( const char *text, mcpu_ar_options *options ) +{ + const char *p = text; + int primary = 0; + + memset( options, 0, sizeof( *options ) ); + if( *p == '-' ) + ++p; + if( !*p ) + return( -1 ); + + while( *p ) + { + switch( *p ) + { + case 'r': case 'q': case 'd': case 't': case 'p': case 'x': + if( primary && options->operation != *p ) + return( -1 ); + options->operation = *p; + primary = 1; + break; + case 's': + options->index = 1; + break; + case 'c': + options->create = 1; + break; + case 'v': + options->verbose = 1; + break; + case 'D': + break; /* Deterministic mode is always active. */ + default: + return( -1 ); + } + ++p; + } + + if( !primary ) + { + if( options->index ) + options->operation = 's'; + else + return( -1 ); + } + return( 0 ); + +} /* End of parse_operation() */ + + +static int +archive_exists( const char *path ) +{ + struct stat st; + return( stat( path, &st ) == 0 ); + +} /* End of archive_exists() */ + + +static int +selector_name( const char *arg, const char **name ) +{ + const char *p = mcpu_archive_basename( arg ); + + if( !p || !*p ) + return( -1 ); + *name = p; + return( 0 ); + +} /* End of selector_name() */ + + +static int +name_selected( const char *name, int argc, char **argv, int *found ) +{ + int i; + + if( argc == 0 ) + return( 1 ); + + for( i = 0; i < argc; ++i ) + { + const char *selector; + + if( selector_name( argv[i], &selector ) ) + continue; + if( !strcmp( name, selector ) ) + { + if( found ) + found[i] = 1; + return( 1 ); + } + } + return( 0 ); + +} /* End of name_selected() */ + + +static int +check_all_found( int argc, char **argv, int *found, const char *prog ) +{ + int i; + int rc = 0; + + for( i = 0; i < argc; ++i ) + if( !found[i] ) + { + fprintf( stderr, "%s: member not found: %s\n", prog, argv[i] ); + rc = -1; + } + return( rc ); + +} /* End of check_all_found() */ + + +static int +replace_member( mcpu_archive *archive, mcpu_archive_member *member, + int verbose ) +{ + size_t i; + + for( i = 0; i < archive->member_count; ++i ) + if( !strcmp( archive->members[i].name, member->name ) ) + { + if( verbose ) + fprintf( stderr, "r - %s\n", member->name ); + mcpu_archive_member_free( &archive->members[i] ); + archive->members[i] = *member; + memset( member, 0, sizeof( *member ) ); + return( 0 ); + } + return( 1 ); + +} /* End of replace_member() */ + + +static int +operation_add( mcpu_archive *archive, char operation, + int argc, char **argv, int verbose, + const char *prog, char *err, size_t errsz ) +{ + int i; + + if( argc == 0 ) + { + fprintf( stderr, "%s: operation %c requires at least one member\n", + prog, operation ); + return( -1 ); + } + + for( i = 0; i < argc; ++i ) + { + mcpu_archive_member member; + int rr = 1; + + memset( &member, 0, sizeof( member ) ); + if( mcpu_archive_member_load( argv[i], &member, err, errsz ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, argv[i], err ); + return( -1 ); + } + + if( operation == 'r' ) + rr = replace_member( archive, &member, verbose ); + if( rr > 0 ) + { + if( verbose ) + fprintf( stderr, "%c - %s\n", operation, member.name ); + if( mcpu_archive_append( archive, &member, err, errsz ) ) + { + fprintf( stderr, "%s: %s\n", prog, err ); + mcpu_archive_member_free( &member ); + return( -1 ); + } + } + } + return( 0 ); + +} /* End of operation_add() */ + + +static int +operation_delete( mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found; + size_t i, out; + + if( argc == 0 ) + { + fprintf( stderr, "%s: operation d requires at least one member\n", prog ); + return( -1 ); + } + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + + out = 0; + for( i = 0; i < archive->member_count; ++i ) + { + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + fprintf( stderr, "d - %s\n", archive->members[i].name ); + mcpu_archive_member_free( &archive->members[i] ); + continue; + } + if( out != i ) + { + archive->members[out] = archive->members[i]; + memset( &archive->members[i], 0, sizeof( archive->members[i] ) ); + } + ++out; + } + archive->member_count = out; + + i = check_all_found( argc, argv, found, prog ) ? 1 : 0; + free( found ); + return( i ? -1 : 0 ); + +} /* End of operation_delete() */ + + +static int +operation_list( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + printf( "rw-r--r-- 0/0 %10zu Jan 1 00:00 1970 %s\n", + archive->members[i].size, archive->members[i].name ); + else + printf( "%s\n", archive->members[i].name ); + } + + rc = check_all_found( argc, argv, found, prog ); + free( found ); + return( rc ); + +} /* End of operation_list() */ + + +static int +operation_print( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc = 0; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + fprintf( stderr, "p - %s\n", archive->members[i].name ); + if( archive->members[i].size && + fwrite( archive->members[i].data, 1, archive->members[i].size, + stdout ) != archive->members[i].size ) + { + fprintf( stderr, "%s: cannot write stdout: %s\n", + prog, strerror( errno ) ); + rc = -1; + break; + } + } + + if( !rc && check_all_found( argc, argv, found, prog ) ) + rc = -1; + free( found ); + return( rc ); + +} /* End of operation_print() */ + + +static int +operation_extract( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc = 0; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + FILE *fp; + + if( verbose ) + fprintf( stderr, "x - %s\n", archive->members[i].name ); + fp = fopen( archive->members[i].name, "wb" ); + if( !fp ) + { + fprintf( stderr, "%s: cannot create %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + rc = -1; + break; + } + if( archive->members[i].size && + fwrite( archive->members[i].data, 1, archive->members[i].size, + fp ) != archive->members[i].size ) + { + fprintf( stderr, "%s: cannot write %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + fclose( fp ); + rc = -1; + break; + } + if( fclose( fp ) ) + { + fprintf( stderr, "%s: cannot close %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + rc = -1; + break; + } + chmod( archive->members[i].name, 0644 ); + } + + if( !rc && check_all_found( argc, argv, found, prog ) ) + rc = -1; + free( found ); + return( rc ); + +} /* End of operation_extract() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argc > 0 ? argv[0] : "mcpu-ar"; + mcpu_ar_options options; + const char *archive_path; + mcpu_archive archive; + char err[512]; + int exists; + int member_argc; + char **member_argv; + int mutation = 0; + int rc = EXIT_FAILURE; + + if( argc == 2 && !strcmp( argv[1], "--help" ) ) + { + usage( stdout, prog ); + return( EXIT_SUCCESS ); + } + if( argc >= 2 && + (!strcmp( argv[1], "--version" ) || + !strcmp( argv[1], "--dump-version" )) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( argv[1], &version_action ); + return( mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE ); + } + if( argc < 3 || parse_operation( argv[1], &options ) ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + + archive_path = argv[2]; + member_argc = argc - 3; + member_argv = argv + 3; + exists = archive_exists( archive_path ); + mcpu_archive_init( &archive ); + + if( exists ) + { + err[0] = 0; + if( mcpu_archive_read( archive_path, &archive, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, err ); + goto out; + } + } + else if( options.operation != 'r' && options.operation != 'q' ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, strerror( ENOENT ) ); + goto out; + } + + err[0] = 0; + switch( options.operation ) + { + case 'r': + case 'q': + if( operation_add( &archive, options.operation, member_argc, member_argv, + options.verbose, prog, err, sizeof( err ) ) ) + goto out; + mutation = 1; + break; + case 'd': + if( operation_delete( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + mutation = 1; + break; + case 't': + if( operation_list( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 'p': + if( operation_print( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 'x': + if( operation_extract( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 's': + if( member_argc ) + { + fprintf( stderr, "%s: operation s does not accept member operands\n", + prog ); + goto out; + } + mutation = 1; + options.index = 1; + break; + default: + goto out; + } + + if( mutation ) + { + int write_index = options.index || archive.had_symbol_index; + + err[0] = 0; + if( mcpu_archive_write( archive_path, &archive, write_index, + err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, err ); + goto out; + } + } + + rc = EXIT_SUCCESS; + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-archive.c b/utils/mcpu-archive.c new file mode 100644 index 0000000..e10dd5c --- /dev/null +++ b/utils/mcpu-archive.c @@ -0,0 +1,1025 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-asm.h" +#include "mcpu-melf.h" +#include "mcpu-object.h" + + +#define MCPU_AR_MAGIC "!<arch>\n" +#define MCPU_AR_MAGIC_SIZE 8U +#define MCPU_AR_HEADER_SIZE 60U + + +typedef struct mcpu_ar_raw_header +{ + char name[16]; + char date[12]; + char uid[6]; + char gid[6]; + char mode[8]; + char size[10]; + char fmag[2]; +} mcpu_ar_raw_header; + + +static int +add_size( size_t a, size_t b, size_t *result ) +{ + if( b > SIZE_MAX - a ) + return( -1 ); + *result = a + b; + return( 0 ); + +} /* End of add_size() */ + + +static int +mul_size( size_t a, size_t b, size_t *result ) +{ + if( a && b > SIZE_MAX / a ) + return( -1 ); + *result = a * b; + return( 0 ); + +} /* End of mul_size() */ + + +static char * +copy_string( const char *s, size_t n ) +{ + char *p = (char *)malloc( n + 1 ); + + if( !p ) + return( NULL ); + memcpy( p, s, n ); + p[n] = 0; + return( p ); + +} /* End of copy_string() */ + + +const char * +mcpu_archive_basename( const char *path ) +{ + const char *p; + + if( !path ) + return( NULL ); + p = strrchr( path, '/' ); + return( p ? p + 1 : path ); + +} /* End of mcpu_archive_basename() */ + + +static int +valid_member_name( const char *name ) +{ + const unsigned char *p = (const unsigned char *)name; + + if( !name || !*name || !strcmp( name, "." ) || !strcmp( name, ".." ) ) + return( 0 ); + + while( *p ) + { + if( *p == '/' || *p == '\n' || *p == '\r' || *p == 0x7f ) + return( 0 ); + ++p; + } + return( 1 ); + +} /* End of valid_member_name() */ + + +void +mcpu_archive_member_free( mcpu_archive_member *member ) +{ + size_t i; + + if( !member ) + return; + + free( member->name ); + free( member->data ); + for( i = 0; i < member->symbol_count; ++i ) + free( member->symbols[i] ); + free( member->symbols ); + memset( member, 0, sizeof( *member ) ); + +} /* End of mcpu_archive_member_free() */ + + +void +mcpu_archive_init( mcpu_archive *archive ) +{ + if( archive ) + memset( archive, 0, sizeof( *archive ) ); + +} /* End of mcpu_archive_init() */ + + +void +mcpu_archive_free( mcpu_archive *archive ) +{ + size_t i; + + if( !archive ) + return; + + for( i = 0; i < archive->member_count; ++i ) + mcpu_archive_member_free( &archive->members[i] ); + free( archive->members ); + memset( archive, 0, sizeof( *archive ) ); + +} /* End of mcpu_archive_free() */ + + +static int +member_add_symbol( mcpu_archive_member *member, const char *name, + char *err, size_t errsz ) +{ + char **q; + char *s; + + q = (char **)realloc( member->symbols, + (member->symbol_count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + s = strdup( name ); + if( !s ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + member->symbols = q; + member->symbols[member->symbol_count++] = s; + return( 0 ); + +} /* End of member_add_symbol() */ + + +static int +member_collect_symbols( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + cpu_asm_object object; + size_t i; + + memset( &object, 0, sizeof( object ) ); + if( cpu_object_read( path, &object, err, errsz ) ) + return( -1 ); + + for( i = 0; i < object.symbol_count; ++i ) + { + const cpu_asm_symbol *s = &object.symbols[i]; + + if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) + continue; + if( member_add_symbol( member, s->name, err, errsz ) ) + { + cpu_asm_object_free( &object ); + return( -1 ); + } + } + + cpu_asm_object_free( &object ); + return( 0 ); + +} /* End of member_collect_symbols() */ + + + + + +static int +member_collect_symbols_data( const unsigned char *data, size_t size, + mcpu_archive_member *member, + char *err, size_t errsz ) +{ + cpu_asm_object object; + size_t i; + + memset( &object, 0, sizeof( object ) ); + if( cpu_object_read_data(data, size, &object, err, errsz) ) + return( -1 ); + + for( i = 0; i < object.symbol_count; ++i ) + { + const cpu_asm_symbol *s = &object.symbols[i]; + + if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) + continue; + if( member_add_symbol(member, s->name, err, errsz) ) + { + cpu_asm_object_free( &object ); + return( -1 ); + } + } + + cpu_asm_object_free( &object ); + return( 0 ); + +} /* End of member_collect_symbols_data() */ + + +int +mcpu_archive_member_load( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + const char *name; + unsigned char *data = NULL; + size_t size = 0; + + if( !path || !member ) + return( -1 ); + memset( member, 0, sizeof( *member ) ); + + name = mcpu_archive_basename( path ); + if( !valid_member_name( name ) ) + { + snprintf( err, errsz, "invalid archive member name: %s", + name ? name : "<null>" ); + return( -1 ); + } + + if( cpu_melf_readall( path, &data, &size, err, errsz ) ) + return( -1 ); + + member->name = strdup( name ); + member->data = data; + member->size = size; + if( !member->name ) + { + snprintf( err, errsz, "out of memory" ); + mcpu_archive_member_free( member ); + return( -1 ); + } + + if( member_collect_symbols( path, member, err, errsz ) ) + { + mcpu_archive_member_free( member ); + return( -1 ); + } + return( 0 ); + +} /* End of mcpu_archive_member_load() */ + + +int +mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + mcpu_archive_member *q; + + if( !archive || !member ) + return( -1 ); + + q = (mcpu_archive_member *)realloc( archive->members, + (archive->member_count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + archive->members = q; + archive->members[archive->member_count++] = *member; + memset( member, 0, sizeof( *member ) ); + return( 0 ); + +} /* End of mcpu_archive_append() */ + + +static int +parse_decimal_field( const char *p, size_t n, size_t *value ) +{ + size_t i = 0; + size_t v = 0; + int any = 0; + + while( i < n && p[i] == ' ' ) + ++i; + while( i < n && p[i] != ' ' ) + { + unsigned d; + + if( p[i] < '0' || p[i] > '9' ) + return( -1 ); + d = (unsigned)(p[i] - '0'); + if( v > (SIZE_MAX - d) / 10 ) + return( -1 ); + v = v * 10 + d; + any = 1; + ++i; + } + while( i < n ) + { + if( p[i] != ' ' ) + return( -1 ); + ++i; + } + + if( !any ) + return( -1 ); + *value = v; + return( 0 ); + +} /* End of parse_decimal_field() */ + + +static int +raw_name( const mcpu_ar_raw_header *h, char *name, size_t namesz ) +{ + size_t n = sizeof( h->name ); + + while( n && h->name[n - 1] == ' ' ) + --n; + if( n + 1 > namesz ) + return( -1 ); + memcpy( name, h->name, n ); + name[n] = 0; + return( 0 ); + +} /* End of raw_name() */ + + +static int +long_name_at( const unsigned char *tab, size_t tabsize, size_t off, + char **name, char *err, size_t errsz ) +{ + size_t end; + + if( off >= tabsize ) + { + snprintf( err, errsz, "bad GNU archive long-name offset" ); + return( -1 ); + } + + end = off; + while( end < tabsize && tab[end] != '\n' && tab[end] != 0 ) + ++end; + if( end == tabsize ) + { + snprintf( err, errsz, "unterminated GNU archive long name" ); + return( -1 ); + } + + if( end > off && tab[end - 1] == '/' ) + --end; + *name = copy_string( (const char *)tab + off, end - off ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + return( 0 ); + +} /* End of long_name_at() */ + + +static int +read_member_name( const mcpu_ar_raw_header *h, + const unsigned char *payload, size_t payload_size, + const unsigned char *longtab, size_t longtab_size, + char **name, size_t *name_prefix, + int *special, char *err, size_t errsz ) +{ + char raw[17]; + size_t n; + + *name = NULL; + *name_prefix = 0; + *special = 0; + + if( raw_name( h, raw, sizeof( raw ) ) ) + { + snprintf( err, errsz, "bad archive member name" ); + return( -1 ); + } + + if( !strcmp( raw, "/" ) || !strcmp( raw, "/SYM64/" ) ) + { + *special = 1; + return( 0 ); + } + if( !strcmp( raw, "//" ) ) + { + *special = 2; + return( 0 ); + } + + if( !strncmp( raw, "#1/", 3 ) ) + { + if( parse_decimal_field( raw + 3, strlen( raw + 3 ), &n ) || + n == 0 || n > payload_size ) + { + snprintf( err, errsz, "bad BSD archive extended name" ); + return( -1 ); + } + *name = copy_string( (const char *)payload, n ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *name_prefix = n; + } + else if( raw[0] == '/' && raw[1] >= '0' && raw[1] <= '9' ) + { + size_t off; + + if( !longtab || parse_decimal_field( raw + 1, strlen( raw + 1 ), &off ) || + long_name_at( longtab, longtab_size, off, name, err, errsz ) ) + return( -1 ); + } + else + { + n = strlen( raw ); + if( n && raw[n - 1] == '/' ) + --n; + *name = copy_string( raw, n ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + + if( !valid_member_name( *name ) ) + { + snprintf( err, errsz, "invalid archive member name: %s", *name ); + free( *name ); + *name = NULL; + return( -1 ); + } + return( 0 ); + +} /* End of read_member_name() */ + + +int +mcpu_archive_read( const char *path, mcpu_archive *archive, + char *err, size_t errsz ) +{ + unsigned char *file = NULL; + size_t file_size = 0; + size_t off; + const unsigned char *longtab = NULL; + size_t longtab_size = 0; + int rc = -1; + + if( !path || !archive ) + return( -1 ); + mcpu_archive_init( archive ); + + if( cpu_melf_readall( path, &file, &file_size, err, errsz ) ) + return( -1 ); + if( file_size < MCPU_AR_MAGIC_SIZE || + memcmp( file, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) ) + { + snprintf( err, errsz, "not a UNIX archive" ); + goto out; + } + + off = MCPU_AR_MAGIC_SIZE; + while( off < file_size ) + { + const mcpu_ar_raw_header *h; + size_t stored_size; + size_t next; + const unsigned char *payload; + char *name = NULL; + size_t name_prefix = 0; + int special = 0; + mcpu_archive_member member; + + memset( &member, 0, sizeof( member ) ); + + if( file_size - off < MCPU_AR_HEADER_SIZE ) + { + snprintf( err, errsz, "truncated archive member header" ); + goto out; + } + h = (const mcpu_ar_raw_header *)(file + off); + if( h->fmag[0] != '`' || h->fmag[1] != '\n' || + parse_decimal_field( h->size, sizeof( h->size ), &stored_size ) ) + { + snprintf( err, errsz, "bad archive member header" ); + goto out; + } + + if( stored_size > file_size - off - MCPU_AR_HEADER_SIZE ) + { + snprintf( err, errsz, "truncated archive member" ); + goto out; + } + payload = file + off + MCPU_AR_HEADER_SIZE; + + if( read_member_name( h, payload, stored_size, longtab, longtab_size, + &name, &name_prefix, &special, err, errsz ) ) + goto out; + + if( special == 1 ) + archive->had_symbol_index = 1; + else if( special == 2 ) + { + longtab = payload; + longtab_size = stored_size; + } + else + { + if( name_prefix > stored_size ) + { + free( name ); + snprintf( err, errsz, "bad archive extended member size" ); + goto out; + } + + member.name = name; + member.size = stored_size - name_prefix; + member.data = (unsigned char *)malloc( member.size ? member.size : 1 ); + if( !member.data ) + { + snprintf( err, errsz, "out of memory" ); + mcpu_archive_member_free( &member ); + goto out; + } + if( member.size ) + memcpy( member.data, payload + name_prefix, member.size ); + + if( member_collect_symbols_data( member.data, member.size, &member, + err, errsz ) || + mcpu_archive_append( archive, &member, err, errsz ) ) + { + mcpu_archive_member_free( &member ); + goto out; + } + } + + if( add_size( off, MCPU_AR_HEADER_SIZE, &next ) || + add_size( next, stored_size, &next ) ) + { + snprintf( err, errsz, "archive offset overflow" ); + goto out; + } + if( stored_size & 1 ) + { + if( next >= file_size || file[next] != '\n' ) + { + snprintf( err, errsz, "bad archive member padding" ); + goto out; + } + ++next; + } + off = next; + } + + rc = 0; + +out: + free( file ); + if( rc ) + mcpu_archive_free( archive ); + return( rc ); + +} /* End of mcpu_archive_read() */ + + +static int +short_member_name( const char *name ) +{ + const unsigned char *p = (const unsigned char *)name; + size_t n = strlen( name ); + + if( n == 0 || n > 15 ) + return( 0 ); + while( *p ) + { + if( *p == ' ' || *p == '/' || *p == '\t' ) + return( 0 ); + ++p; + } + return( 1 ); + +} /* End of short_member_name() */ + + +static int +member_stored_size( const mcpu_archive_member *member, size_t *size ) +{ + size_t n = short_member_name( member->name ) ? 0 : strlen( member->name ); + + return( add_size( n, member->size, size ) ); + +} /* End of member_stored_size() */ + + +static int +format_field( char *dst, size_t n, const char *text ) +{ + size_t len = strlen( text ); + + if( len > n ) + return( -1 ); + memcpy( dst, text, len ); + memset( dst + len, ' ', n - len ); + return( 0 ); + +} /* End of format_field() */ + + +static int +format_size_field( char *dst, size_t n, size_t value ) +{ + char text[32]; + + snprintf( text, sizeof( text ), "%zu", value ); + return( format_field( dst, n, text ) ); + +} /* End of format_size_field() */ + + +static int +make_header( mcpu_ar_raw_header *h, const char *name, size_t stored_size, + int special ) +{ + char namebuf[32]; + + memset( h, ' ', sizeof( *h ) ); + + if( special ) + { + if( format_field( h->name, sizeof( h->name ), name ) ) + return( -1 ); + } + else if( short_member_name( name ) ) + { + snprintf( namebuf, sizeof( namebuf ), "%s/", name ); + if( format_field( h->name, sizeof( h->name ), namebuf ) ) + return( -1 ); + } + else + { + snprintf( namebuf, sizeof( namebuf ), "#1/%zu", strlen( name ) ); + if( format_field( h->name, sizeof( h->name ), namebuf ) ) + return( -1 ); + } + + if( format_field( h->date, sizeof( h->date ), "0" ) || + format_field( h->uid, sizeof( h->uid ), "0" ) || + format_field( h->gid, sizeof( h->gid ), "0" ) || + format_field( h->mode, sizeof( h->mode ), special ? "0" : "644" ) || + format_size_field( h->size, sizeof( h->size ), stored_size ) ) + return( -1 ); + + h->fmag[0] = '`'; + h->fmag[1] = '\n'; + return( 0 ); + +} /* End of make_header() */ + + +static int +write_bytes( FILE *fp, const void *data, size_t size ) +{ + return( size && fwrite( data, 1, size, fp ) != size ? -1 : 0 ); + +} /* End of write_bytes() */ + + +static void +put_be32( unsigned char *p, uint32_t v ) +{ + p[0] = (unsigned char)(v >> 24); + p[1] = (unsigned char)(v >> 16); + p[2] = (unsigned char)(v >> 8); + p[3] = (unsigned char)v; + +} /* End of put_be32() */ + + +static int +symbol_index_size( const mcpu_archive *archive, size_t *count, + size_t *payload_size, char *err, size_t errsz ) +{ + size_t i, j; + size_t c = 0; + size_t names = 0; + size_t offsets; + size_t total; + + for( i = 0; i < archive->member_count; ++i ) + { + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + size_t n = strlen( archive->members[i].symbols[j] ) + 1; + + if( add_size( names, n, &names ) || c == SIZE_MAX ) + { + snprintf( err, errsz, "archive symbol index is too large" ); + return( -1 ); + } + ++c; + } + } + + if( c > UINT32_MAX || mul_size( c, 4, &offsets ) || + add_size( 4, offsets, &total ) || add_size( total, names, &total ) ) + { + snprintf( err, errsz, "archive symbol index is too large" ); + return( -1 ); + } + + *count = c; + *payload_size = total; + return( 0 ); + +} /* End of symbol_index_size() */ + + +static int +compute_member_offsets( const mcpu_archive *archive, int write_index, + size_t index_size, uint32_t **offsets, + char *err, size_t errsz ) +{ + uint32_t *v; + size_t off = MCPU_AR_MAGIC_SIZE; + size_t i; + + v = (uint32_t *)calloc( archive->member_count ? archive->member_count : 1, + sizeof( *v ) ); + if( !v ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + if( write_index ) + { + if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || + add_size( off, index_size, &off ) ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + if( index_size & 1 ) + { + if( off == SIZE_MAX ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + ++off; + } + } + + for( i = 0; i < archive->member_count; ++i ) + { + size_t stored; + + if( off > UINT32_MAX || member_stored_size( &archive->members[i], &stored ) ) + { + snprintf( err, errsz, "archive is too large for a 32-bit symbol index" ); + free( v ); + return( -1 ); + } + v[i] = (uint32_t)off; + + if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || + add_size( off, stored, &off ) ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + if( stored & 1 ) + { + if( off == SIZE_MAX ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + ++off; + } + } + + *offsets = v; + return( 0 ); + +} /* End of compute_member_offsets() */ + + +static int +write_armap( FILE *fp, const mcpu_archive *archive, + const uint32_t *member_offsets, size_t count, + size_t payload_size, char *err, size_t errsz ) +{ + mcpu_ar_raw_header h; + unsigned char *payload; + size_t pos; + size_t i, j, k = 0; + + if( make_header( &h, "/", payload_size, 1 ) ) + { + snprintf( err, errsz, "archive symbol index header is too large" ); + return( -1 ); + } + + payload = (unsigned char *)malloc( payload_size ? payload_size : 1 ); + if( !payload ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + put_be32( payload, (uint32_t)count ); + pos = 4; + for( i = 0; i < archive->member_count; ++i ) + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + put_be32( payload + pos, member_offsets[i] ); + pos += 4; + ++k; + } + + for( i = 0; i < archive->member_count; ++i ) + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + size_t n = strlen( archive->members[i].symbols[j] ) + 1; + memcpy( payload + pos, archive->members[i].symbols[j], n ); + pos += n; + } + + if( k != count || pos != payload_size || + write_bytes( fp, &h, sizeof( h ) ) || + write_bytes( fp, payload, payload_size ) || + (payload_size & 1 && fputc( '\n', fp ) == EOF) ) + { + snprintf( err, errsz, "cannot write archive symbol index: %s", + strerror( errno ? errno : EIO ) ); + free( payload ); + return( -1 ); + } + + free( payload ); + return( 0 ); + +} /* End of write_symbol_index() */ + + +static int +write_member( FILE *fp, const mcpu_archive_member *member, + char *err, size_t errsz ) +{ + mcpu_ar_raw_header h; + size_t stored; + int shortname = short_member_name( member->name ); + + if( member_stored_size( member, &stored ) || + make_header( &h, member->name, stored, 0 ) ) + { + snprintf( err, errsz, "archive member is too large: %s", member->name ); + return( -1 ); + } + + if( write_bytes( fp, &h, sizeof( h ) ) || + (!shortname && write_bytes( fp, member->name, strlen( member->name ) )) || + write_bytes( fp, member->data, member->size ) || + (stored & 1 && fputc( '\n', fp ) == EOF) ) + { + snprintf( err, errsz, "cannot write archive member %s: %s", + member->name, strerror( errno ? errno : EIO ) ); + return( -1 ); + } + return( 0 ); + +} /* End of write_member() */ + + +int +mcpu_archive_write( const char *path, const mcpu_archive *archive, + int write_symbol_index, char *err, size_t errsz ) +{ + char *tmp; + size_t tmpsz; + int fd = -1; + FILE *fp = NULL; + size_t symbol_count = 0; + size_t index_size = 0; + uint32_t *member_offsets = NULL; + size_t i; + int rc = -1; + + if( !path || !archive ) + return( -1 ); + + if( write_symbol_index && + symbol_index_size( archive, &symbol_count, &index_size, err, errsz ) ) + return( -1 ); + if( write_symbol_index && + compute_member_offsets( archive, 1, index_size, + &member_offsets, err, errsz ) ) + return( -1 ); + + if( add_size( strlen( path ), sizeof( ".tmp.XXXXXX" ), &tmpsz ) ) + { + snprintf( err, errsz, "archive pathname is too long" ); + goto out; + } + tmp = (char *)malloc( tmpsz ); + if( !tmp ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + snprintf( tmp, tmpsz, "%s.tmp.XXXXXX", path ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "cannot create archive temporary file: %s", + strerror( errno ) ); + free( tmp ); + goto out; + } + fp = fdopen( fd, "wb" ); + if( !fp ) + { + snprintf( err, errsz, "cannot open archive temporary stream: %s", + strerror( errno ) ); + close( fd ); + fd = -1; + unlink( tmp ); + free( tmp ); + goto out; + } + fd = -1; + + if( write_bytes( fp, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) || + (write_symbol_index && + write_armap( fp, archive, member_offsets, symbol_count, + index_size, err, errsz )) ) + goto write_fail; + + for( i = 0; i < archive->member_count; ++i ) + if( write_member( fp, &archive->members[i], err, errsz ) ) + goto write_fail; + + if( fflush( fp ) || fsync( fileno( fp ) ) || fclose( fp ) ) + { + fp = NULL; + snprintf( err, errsz, "cannot finalize archive: %s", strerror( errno ) ); + unlink( tmp ); + free( tmp ); + goto out; + } + fp = NULL; + + if( rename( tmp, path ) ) + { + snprintf( err, errsz, "cannot install archive %s: %s", + path, strerror( errno ) ); + unlink( tmp ); + free( tmp ); + goto out; + } + free( tmp ); + rc = 0; + goto out; + +write_fail: + if( !err[0] ) + snprintf( err, errsz, "cannot write archive: %s", strerror( errno ) ); + fclose( fp ); + fp = NULL; + unlink( tmp ); + free( tmp ); + +out: + if( fp ) + fclose( fp ); + if( fd >= 0 ) + close( fd ); + free( member_offsets ); + return( rc ); + +} /* End of mcpu_archive_write() */ diff --git a/utils/mcpu-archive.h b/utils/mcpu-archive.h new file mode 100644 index 0000000..80634f0 --- /dev/null +++ b/utils/mcpu-archive.h @@ -0,0 +1,41 @@ +#ifndef __MCPU_ARCHIVE_H +#define __MCPU_ARCHIVE_H + +#include <stddef.h> + + +typedef struct mcpu_archive_member +{ + char *name; + unsigned char *data; + size_t size; + char **symbols; + size_t symbol_count; +} mcpu_archive_member; + + +typedef struct mcpu_archive +{ + mcpu_archive_member *members; + size_t member_count; + int had_symbol_index; +} mcpu_archive; + + +void mcpu_archive_init( mcpu_archive *archive ); +void mcpu_archive_free( mcpu_archive *archive ); +void mcpu_archive_member_free( mcpu_archive_member *member ); + +int mcpu_archive_read( const char *path, mcpu_archive *archive, + char *err, size_t errsz ); +int mcpu_archive_write( const char *path, const mcpu_archive *archive, + int write_symbol_index, char *err, size_t errsz ); + +int mcpu_archive_member_load( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ); +int mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member, + char *err, size_t errsz ); + +const char *mcpu_archive_basename( const char *path ); + +#endif diff --git a/utils/mcpu-as.c b/utils/mcpu-as.c new file mode 100644 index 0000000..a3b38c1 --- /dev/null +++ b/utils/mcpu-as.c @@ -0,0 +1,612 @@ +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-asm.h" +#include "mcpu-object.h" +#include "mcpu-version.h" + +#define CPU_AS_LINE_MAX 2147483647ULL + +typedef struct source_marker +{ + size_t physical_line; + uint64_t logical_line; + char *filename; +} source_marker; + +typedef struct source_map +{ + source_marker *marker; + size_t count; + size_t capacity; +} source_map; + +static char * +readall( const char *name ) +{ + FILE *f; + long z; + char *p; + + f = fopen( name, "rb" ); + if( !f ) + return( NULL ); + if( fseek( f, 0, SEEK_END ) != 0 ) + { + fclose( f ); + return( NULL ); + } + z = ftell( f ); + if( z < 0 ) + { + fclose( f ); + return( NULL ); + } + rewind( f ); + p = (char *)malloc( (size_t)z + 1U ); + if( !p ) + { + fclose( f ); + return( NULL ); + } + if( fread( p, 1, (size_t)z, f ) != (size_t)z ) + { + free( p ); + fclose( f ); + return( NULL ); + } + p[z] = 0; + fclose( f ); + return( p ); +} + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] -o output.o input.s\n\n" + "Assemble UTF-8 LibMCPU source into a MELF ET_REL relocatable object.\n\n" + "Options:\n" + " -o <file> - write output to <file>\n" + " -v, --verbose - print output-object summary\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n\n" + "Exactly one input source is required. The input file and -o option may\n" + "appear in either order.\n", + name); +} + +static void +source_map_free( source_map *map ) +{ + size_t i; + + if( !map ) + return; + for( i = 0; i < map->count; ++i ) + free( map->marker[i].filename ); + free( map->marker ); + memset( map, 0, sizeof( *map ) ); +} + +static int +source_map_add( source_map *map, size_t physical_line, uint64_t logical_line, + const char *filename) +{ + source_marker *q; + size_t capacity; + + if( map->count == map->capacity ) + { + capacity = map->capacity ? map->capacity * 2U : 8U; + q = ( source_marker * ) realloc( map->marker, capacity *sizeof( *q ) ); + if( !q ) + return( -1 ); + map->marker = q; + map->capacity = capacity; + } + + map->marker[map->count].physical_line = physical_line; + map->marker[map->count].logical_line = logical_line; + map->marker[map->count].filename = strdup( filename ? filename : "<input>" ); + if( !map->marker[map->count].filename ) + return( -1 ); + ++map->count; + return( 0 ); +} + +static const source_marker * +source_map_lookup( const source_map *map, size_t physical_line ) +{ + size_t i; + const source_marker *found = NULL; + + for( i = 0; i < map->count; ++i ) + { + if( map->marker[i].physical_line > physical_line ) + break; + found = &map->marker[i]; + } + return( found ); +} + +static uint64_t +source_map_logical_line( const source_marker *m, size_t physical_line ) +{ + uint64_t delta; + + if( !m || physical_line < m->physical_line ) + return (uint64_t)physical_line; + delta = (uint64_t)(physical_line - m->physical_line); + if( UINT64_MAX - m->logical_line < delta ) + return( UINT64_MAX ); + return( m->logical_line + delta ); +} + +static int +horizontal_space( int c ) +{ + return( c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r' ); +} + +/* + * Classify source-control lines left by a preprocessor. + * + * Return values: + * 0 ordinary source line + * 1 other '#' line: ignore its contents, but count the line normally + * 2 #pragma / #lang: ignore its contents and do not advance logical line + * 3 preprocessor line marker: # decimal ["filename"] [flags...] + * -1 malformed line marker + * + * A line marker applies to the physical source line immediately following it. + * Its decimal number is always base 10 and is restricted to 0..2147483647. + * If the filename is omitted, the previous logical filename is retained. Any + * text following the closing filename quote is deliberately ignored: those are + * preprocessor flags for later consumers and have no assembler semantics. + * + * The historical #line spelling is intentionally not recognized here. Like + * every other unrecognized '#' line, it is discarded without affecting + * assembly syntax and counts as one ordinary logical source line. + */ +static int +parse_source_control( const char *line, uint64_t *next_line, char **new_filename, + char *why, size_t whysz ) +{ + const char *p = line; + const char *digits; + const char *start; + uint64_t value = 0; + size_t n; + char *filename = NULL; + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p != '#' ) + return( 0 ); + ++p; + while( horizontal_space( (unsigned char)*p ) ) + ++p; + + if( !strncmp( p, "pragma", 6 ) && + (!p[6] || horizontal_space( (unsigned char)p[6] )) ) + return( 2 ); + if( !strncmp( p, "lang", 4 ) && + (!p[4] || horizontal_space( (unsigned char)p[4] )) ) + return( 2 ); + + if( !isdigit( (unsigned char)*p ) ) + return( 1 ); + + digits = p; + while( isdigit( (unsigned char)*p ) ) + ++p; + + if( *p && !horizontal_space( (unsigned char)*p ) ) + return( 1 ); + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p && *p != '"' ) + return( 1 ); + + p = digits; + do + { + unsigned int digit = (unsigned int)(*p - '0'); + + if( value > ( CPU_AS_LINE_MAX - digit ) / 10ULL ) + { + snprintf( why, whysz, + "preprocessor line marker value is outside 0..2147483647" ); + return( -1 ); + } + value = value * 10ULL + digit; + ++p; + } + while( isdigit( (unsigned char)*p ) ); + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p == '"' ) + { + ++p; + start = p; + while( *p && *p != '"' ) + ++p; + if( *p != '"' ) + { + snprintf( why, whysz, + "unterminated filename in preprocessor line marker" ); + return( -1 ); + } + n = (size_t)(p - start); + filename = (char *)malloc( n + 1U ); + if( !filename ) + { + snprintf( why, whysz, "out of memory" ); + return( -1 ); + } + memcpy( filename, start, n ); + filename[n] = 0; + } + + *next_line = value; + *new_filename = filename; + return( 3 ); +} + +static void +format_location_error( char *err, size_t errsz, const char *filename, + uint64_t line, const char *message) +{ + snprintf( err, errsz, "%s:%llu: error: %s", + filename ? filename : "<input>", + (unsigned long long)line, + message ? message : "invalid source"); +} + +/* + * cpu_asm's historical line scanner uses strtok_r(..., "\n", ...), which + * skips zero-length fields between adjacent newlines. That is harmless for + * assembly semantics, but it means its internal diagnostic line number does + * not advance across a physically empty source line. + * + * Preprocessor line markers, however, define logical source coordinates, and + * #pragma/#lang lines may be excluded from the logical counter. The physical + * line counter used by cpu_asm therefore has to stay synchronized with the + * source map maintained by mcpu-as, including across empty lines. + * + * Give each physically empty line a single horizontal-space character before + * passing the source to cpu_asm. trim() still treats the line as empty, so no + * assembly semantics change, while strtok_r now returns one token for every + * physical line and its diagnostic counter remains synchronized with the + * source map maintained by mcpu-as. + */ +static char * +copy_preserving_empty_lines( const char *source ) +{ + size_t len, extra = 0, i; + int at_line_start = 1; + char *copy, *q; + + if( !source ) + return( NULL ); + + len = strlen( source ); + for( i = 0; i < len; ++i ) + { + if( source[i] == '\n' ) + { + if( at_line_start ) + ++extra; + at_line_start = 1; + } + else + at_line_start = 0; + } + + if( extra >= SIZE_MAX - len ) + return( NULL ); + copy = (char *)malloc( len + extra + 1U ); + if( !copy ) + return( NULL ); + + q = copy; + at_line_start = 1; + for( i = 0; i < len; ++i ) + { + if( source[i] == '\n' ) + { + if( at_line_start ) + *q++ = ' '; + *q++ = '\n'; + at_line_start = 1; + } + else + { + *q++ = source[i]; + at_line_start = 0; + } + } + *q = 0; + return( copy ); +} + +/* + * Remove preprocessor-control lines from the token stream without changing + * physical line numbering and build a physical->logical source map for + * diagnostics. + */ +static int +prepare_source( const char *source, const char *input_name, char **prepared, + source_map *map, char *err, size_t errsz) +{ + char *copy; + char *line; + size_t physical_line = 1; + const char *logical_file; + + if( !source || !prepared || !map ) + return( -1 ); + + memset( map, 0, sizeof( *map ) ); + copy = copy_preserving_empty_lines( source ); + if( !copy ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + logical_file = input_name ? input_name : "<input>"; + if( source_map_add( map, 1U, 1U, logical_file ) ) + { + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + line = copy; + while( line && *line ) + { + char *end = strchr( line, '\n' ); + char saved = 0; + uint64_t requested_line = 0; + char *requested_file = NULL; + char why[160]; + int parsed; + const source_marker *m; + + if( end ) + { + saved = *end; + *end = 0; + } + + why[0] = 0; + parsed = parse_source_control( line, &requested_line, &requested_file, + why, sizeof( why ) ); + if( parsed < 0 ) + { + m = source_map_lookup( map, physical_line ); + format_location_error( err, errsz, + m ? m->filename : logical_file, + source_map_logical_line( m, physical_line ), why); + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + return( -1 ); + } + + if( parsed > 0 ) + { + size_t len = strlen( line ); + + memset( line, ' ', len ); + if( parsed == 3 ) + { + const source_marker *current = source_map_lookup( map, physical_line ); + const char *next_file = requested_file ? requested_file : + (current ? current->filename : logical_file); + + if( source_map_add( map, physical_line + 1U, requested_line, next_file ) ) + { + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + else if( parsed == 2 ) + { + const source_marker *current = source_map_lookup( map, physical_line ); + uint64_t current_line = source_map_logical_line( current, physical_line ); + const char *current_file = current ? current->filename : logical_file; + + if( source_map_add( map, physical_line + 1U, current_line, current_file ) ) + { + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + } + + free( requested_file ); + if( end ) + { + *end = saved; + line = end + 1; + ++physical_line; + } + else + break; + } + + *prepared = copy; + return( 0 ); +} + +static void +remap_assembler_error( char *err, size_t errsz, const source_map *map, + const char *input_name) +{ + char old[512]; + const char *p; + char *end; + unsigned long long physical; + const source_marker *m; + uint64_t logical; + + if( !err || !*err ) + return; + if( strncmp( err, "line ", 5 ) != 0 ) + return; + + errno = 0; + p = err + 5; + physical = strtoull( p, &end, 10 ); + if( errno || end == p || *end != ':' || physical == 0ULL || + physical > (unsigned long long)SIZE_MAX) + return; + + snprintf( old, sizeof( old ), "%s", end + 1 ); + p = old; + while( *p == ' ' || *p == '\t' ) + ++p; + + m = source_map_lookup( map, (size_t)physical ); + logical = source_map_logical_line( m, (size_t)physical ); + format_location_error( err, errsz, + m ? m->filename : (input_name ? input_name : "<input>"), + logical, p); +} + +int +main( int ac, char **av ) +{ + const char *input = NULL; + const char *output = NULL; + char *src = NULL; + char *prepared = NULL; + char err[512]; + cpu_asm_object object; + source_map map; + int i; + int rc = 1; + int verbose = 0; + + for( i = 1; i < ac; ++i ) + { + mcpu_version_action version_action; + + if( mcpu_version_parse_option(av[i], &version_action) ) + return( mcpu_version_print(av[0], version_action) == 0 ? 0 : 1 ); + + if( !strcmp( av[i], "-h" ) || !strcmp( av[i], "--help" ) ) + { + usage( stdout, av[0] ); + return( 0 ); + } + else if( !strcmp( av[i], "-o" ) ) + { + if(++i >= ac || output ) + { + usage( stderr, av[0] ); + return( 2 ); + } + output = av[i]; + } + else if( !strcmp( av[i], "-v" ) || !strcmp( av[i], "--verbose" ) ) + verbose = 1; + else if( av[i][0] == '-' ) + { + fprintf( stderr, "mcpu-as: unknown option %s\n", av[i] ); + return( 2 ); + } + else + { + if( input ) + { + fprintf( stderr, "mcpu-as: more than one input file\n" ); + return( 2 ); + } + input = av[i]; + } + } + + if( !input || !output ) + { + usage( stderr, av[0] ); + return( 2 ); + } + + __mpu_init(); + src = readall( input ); + if( !src ) + { + perror( input ); + goto out_mpu; + } + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + fprintf( stderr, "mcpu-as: error: assembler source is not valid UTF-8\n" ); + goto out_src; + } + + err[0] = 0; + if( prepare_source( src, input, &prepared, &map, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-as: %s\n", err[0] ? err : "cannot process source" ); + goto out_src; + } + + if( cpu_assemble_object( prepared, &object, err, sizeof( err ) ) ) + { + remap_assembler_error( err, sizeof( err ), &map, input ); + if( !strncmp( err, "line ", 5 ) || strstr( err, ": error: " ) ) + fprintf( stderr, "mcpu-as: %s\n", err ); + else + fprintf( stderr, "mcpu-as: error: %s\n", err ); + goto out_map; + } + + if( cpu_object_write( output, &object, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-as: error: %s\n", err ); + cpu_asm_object_free( &object ); + goto out_map; + } + + if( verbose ) + printf( "MELF ET_REL: %zu symbols, %zu relocations -> %s\n", + object.symbol_count, object.reloc_count, output ); + cpu_asm_object_free( &object ); + rc = 0; + +out_map: + source_map_free( &map ); + free( prepared ); +out_src: + free( src ); +out_mpu: + __mpu_free_context(); + return( rc ); +} diff --git a/utils/mcpu-asm.c b/utils/mcpu-asm.c new file mode 100644 index 0000000..dcc4ead --- /dev/null +++ b/utils/mcpu-asm.c @@ -0,0 +1,4012 @@ +#include <ctype.h> +#include <errno.h> +#include <math.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-asm.h" + +#define ASM_NAME_MAX 64 +#define ASM_EXPR_MAX 256 + +typedef struct +{ + char name[ASM_NAME_MAX]; + __mpu_uint64_t addr; +} label_t; +typedef struct +{ + size_t pos; + char expr[ASM_EXPR_MAX]; +} fixup_t; +typedef struct +{ + char name[ASM_NAME_MAX]; + char expr[ASM_EXPR_MAX]; + int line; + int state; /* 0=not evaluated, 1=evaluating, 2=done */ + __int128 value; +} equ_t; +typedef struct +{ + __mpu_UNIT_t *p; + size_t n, cap; +} buf_t; +typedef struct +{ + label_t *p; + size_t n, cap; +} labels_t; +typedef struct +{ + fixup_t *p; + size_t n, cap; +} fixups_t; +typedef struct +{ + equ_t *p; + size_t n, cap; +} equs_t; + +typedef struct +{ + char name[ASM_NAME_MAX]; + int is_global; + int is_extern; + int is_weak; +} symdecl_t; +typedef struct +{ + symdecl_t *p; + size_t n, cap; +} symdecls_t; + +static int +grow( void **p, size_t *cap, size_t need, size_t unit ) +{ + size_t c = *cap ? *cap : 64; + void *q; + while( c < need ) c *= 2; + q = realloc( *p, c *unit ); + if( !q ) return( -1 ); + *p = q; + *cap = c; + return( 0 ); +} + +static int +put( buf_t *b, const void *p, size_t n ) +{ + if( grow( (void **)&b->p, &b->cap, b->n + n, 1 ) ) return( -1 ); + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +put8( buf_t *b, unsigned v ) +{ + __mpu_UNIT_t x = (__mpu_UNIT_t)v; + return( put( b, &x, 1 ) ); +} + +static int +put16( buf_t *b, unsigned v ) +{ + __mpu_UNIT_t x[2]; + __cpu_code_put_u16( x, (__mpu_uint16_t)v ); + return( put( b, x, 2 ) ); +} + +static int +put64( buf_t *b, __mpu_uint64_t v ) +{ + __mpu_UNIT_t x[8]; + __cpu_code_put_u64( x, v ); + return( put( b, x, 8 ) ); +} + +static char * +trim( char *s ) +{ + char *e; + while( isspace( (unsigned char)*s ) ) s++; + e = s + strlen( s ); + while( e > s && isspace( (unsigned char)e[-1] ) ) *--e = 0; + return( s ); +} + +static int +streqi( const char *a, const char *b ) +{ + for( ; *a && *b; a++, b++ ) if( toupper( (unsigned char)*a ) != toupper( (unsigned char)*b ) ) return( 0 ); + return( !*a && !*b ); +} + +/* ';' starts a comment only outside a quoted assembler string. */ +static void +strip_comment( char *s ) +{ + int quote = 0, esc = 0; + for( ; *s; s++ ) + { + if( quote ) + { + if( esc ) + { + esc = 0; + continue; + } + if( *s == '\\' ) + { + esc = 1; + continue; + } + if( *s == '"' ) + { + quote = 0; + continue; + } + } + else + { + if( *s == '"' ) + { + quote = 1; + continue; + } + if( *s == ';' ) + { + *s = 0; + return; + } + } + } +} + +/* A label colon is recognized only in the first token, never inside operands. */ +static char * +label_colon( char *s ) +{ + char *p = s; + while( *p && !isspace( (unsigned char)*p ) ) + { + if( *p == ':' ) return( p ); + p++; + } + return( 0 ); +} + +static int +addlabel( labels_t *l, const char *n, __mpu_uint64_t a ) +{ + size_t i; + for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) ) return( -1 ); + if( strlen( n ) >= ASM_NAME_MAX ) return( -1 ); + if( grow( (void **)&l->p, &l->cap, l->n + 1, sizeof( *l->p ) ) ) return( -1 ); + strcpy( l->p[l->n].name, n ); + l->p[l->n++].addr = a; + return( 0 ); +} + +static int +findlabel( const labels_t *l, const char *n, __mpu_uint64_t *a ) +{ + size_t i; + for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) ) + { + *a = l->p[i].addr; + return( 0 ); + } + return( -1 ); +} + +static int +addfix( fixups_t *f, size_t pos, const char *expr ) +{ + if( strlen( expr ) >= ASM_EXPR_MAX ) return( -1 ); + if( grow( (void **)&f->p, &f->cap, f->n + 1, sizeof( *f->p ) ) ) return( -1 ); + f->p[f->n].pos = pos; + strcpy( f->p[f->n].expr, expr ); + f->n++; + return( 0 ); +} + +static int +findequ( const equs_t *e, const char *n, size_t *idx ) +{ + size_t i; + for( i = 0; i < e->n; i++ ) if( !strcmp( e->p[i].name, n ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +addequ( equs_t *e, const char *n, const char *expr, int line, char *err, size_t es ) +{ + size_t dummy; + if( !*n || strlen( n ) >= ASM_NAME_MAX || strlen( expr ) >= ASM_EXPR_MAX ) + { + snprintf( err, es, "bad .equ name/expression" ); + return( -1 ); + } + if( !findequ( e, n, &dummy ) ) + { + snprintf( err, es, "duplicate .equ symbol: %s", n ); + return( -1 ); + } + if( grow( (void **)&e->p, &e->cap, e->n + 1, sizeof( *e->p ) ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memset( &e->p[e->n], 0, sizeof( e->p[e->n] ) ); + strcpy( e->p[e->n].name, n ); + strcpy( e->p[e->n].expr, expr ); + e->p[e->n].line = line; + e->n++; + return( 0 ); +} + +/* Split comma-separated arguments while respecting parentheses and quoted strings. */ +static int +splitargs( char *s, char **a, int max ) +{ + int n = 0, depth = 0, quote = 0, esc = 0; + char *p = s, *start = s; + for( ; ; p++ ) + { + int end = ( *p == 0 ); + if( !end ) + { + if( quote ) + { + if( esc ) esc = 0; + else if( *p == '\\' ) esc = 1; + else if( *p == '"' ) quote = 0; + } + else + { + if( *p == '"' ) quote = 1; + else if( *p == '(' ) depth++; + else if( *p == ')' ) depth--; + } + } + if( ( !quote && depth == 0 && *p == ',' ) || end ) + { + if( n >= max ) return( -1 ); + if( !end ) * p = 0; + a[n++] = trim( start ); + if( end ) break; + start = p + 1; + } + } + if( quote || depth != 0 ) return( -1 ); + return( n ); +} + +/* ------------------------- integer expressions ------------------------- */ +typedef struct +{ + const char *p; + equs_t *eqs; + const labels_t *labels; /* optional section-local label namespace */ + char *err; + size_t es; +} expr_parser_t; + +static int eval_expression( const char *, equs_t *, const labels_t *, __int128 *, char *, size_t ); + +static void +expr_skip( expr_parser_t *x ) +{ + while( isspace( (unsigned char)*x->p ) ) x->p++; +} + +static int +symfirst( int c ) +{ + return( isalpha( (unsigned char)c ) || c == '_' || c == '.' || c == '$' ); +} + +static int +symrest( int c ) +{ + return( isalnum( (unsigned char)c ) || c == '_' || c == '.' || c == '$' ); +} + +static int +equ_value( equs_t *eqs, size_t i, __int128 *v, char *err, size_t es ) +{ + equ_t *e = &eqs->p[i]; + if( e->state == 2 ) + { + *v = e->value; + return( 0 ); + } + if( e->state == 1 ) + { + snprintf( err, es, "cyclic .equ definition involving %s", e->name ); + return( -1 ); + } + e->state = 1; + if( eval_expression( e->expr, eqs, 0, &e->value, err, es ) ) + { + e->state = 0; + return( -1 ); + } + e->state = 2; + *v = e->value; + return( 0 ); +} + +static int +expr_symbol( expr_parser_t *x, const char *name, __int128 *v ) +{ + size_t i; + __mpu_uint64_t a; + if( !findequ( x->eqs, name, &i ) ) return( equ_value( x->eqs, i, v, x->err, x->es ) ); + if( x->labels && !findlabel( x->labels, name, &a ) ) + { + *v = ( __int128 ) a; + return( 0 ); + } + snprintf( x->err, x->es, "unknown symbol in expression: %s", name ); + return( -1 ); +} + +static int +expr_primary( expr_parser_t *x, __int128 *v ) +{ + char name[ASM_NAME_MAX], *end; + size_t n = 0; + unsigned long long u; + expr_skip( x ); + if( symfirst( (unsigned char)*x->p ) ) + { + while( symrest( (unsigned char)*x->p ) ) + { + if( n + 1 >= sizeof( name ) ) + { + snprintf( x->err, x->es, "symbol name too long" ); + return( -1 ); + } + name[n++] = *x->p++; + } + name[n] = 0; + return( expr_symbol( x, name, v ) ); + } + errno = 0; + u = strtoull( x->p, &end, 0 ); + if( end == x->p ) + { + snprintf( x->err, x->es, "expected integer or symbol in expression" ); + return( -1 ); + } + if( errno == ERANGE ) + { + snprintf( x->err, x->es, "integer literal out of 64-bit range in expression" ); + return( -1 ); + } + x->p = end; + *v = ( __int128 ) u; + return( 0 ); +} + +/* Forward declarations for recursive-descent parentheses. */ +static int expr_add( expr_parser_t *, __int128 * ); + +static int +expr_atom( expr_parser_t *x, __int128 *v ) +{ + expr_skip( x ); + if( *x->p == '(' ) + { + x->p++; + if( expr_add( x, v ) ) return( -1 ); + expr_skip( x ); + if( *x->p != ')' ) + { + snprintf( x->err, x->es, "missing ')' in expression" ); + return( -1 ); + } + x->p++; + return( 0 ); + } + return( expr_primary( x, v ) ); +} + +static int +expr_unary( expr_parser_t *x, __int128 *v ) +{ + expr_skip( x ); + if( *x->p == '+' ) + { + x->p++; + return( expr_unary( x, v ) ); + } + if( *x->p == '-' ) + { + __int128 t; + x->p++; + if( expr_unary( x, &t ) ) return( -1 ); + if( __builtin_sub_overflow( ( __int128 ) 0, t, v ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + return( 0 ); + } + return( expr_atom( x, v ) ); +} + +static int +expr_mul( expr_parser_t *x, __int128 *v ) +{ + __int128 a, b, r; + char op; + if( expr_unary( x, &a ) ) return( -1 ); + for( ; ; ) + { + expr_skip( x ); + op = *x->p; + if( op != '*' && op != '/' && op != '%' ) break; + x->p++; + if( expr_unary( x, &b ) ) return( -1 ); + if( op == '*' ) + { + if( __builtin_mul_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + else + { + if( !b ) + { + snprintf( x->err, x->es, "division by zero in expression" ); + return( -1 ); + } + if( op == '/' ) r = a / b; + else r = a % b; + } + a = r; + } + *v = a; + return( 0 ); +} + +static int +expr_add( expr_parser_t *x, __int128 *v ) +{ + __int128 a, b, r; + char op; + if( expr_mul( x, &a ) ) return( -1 ); + for( ; ; ) + { + expr_skip( x ); + op = *x->p; + if( op != '+' && op != '-' ) break; + x->p++; + if( expr_mul( x, &b ) ) return( -1 ); + if( op == '+' ) + { + if( __builtin_add_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + else + { + if( __builtin_sub_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + a = r; + } + *v = a; + return( 0 ); +} + +static int +eval_expression( const char *s, equs_t *eqs, const labels_t *labels, __int128 *v, char *err, size_t es ) +{ + expr_parser_t x = + { + s, eqs, labels, err, es + }; + if( expr_add( &x, v ) ) return( -1 ); + expr_skip( &x ); + if( *x.p ) + { + snprintf( err, es, "unexpected token in expression near: %s", x.p ); + return( -1 ); + } + return( 0 ); +} + +static int +eval_i64( const char *s, equs_t *eqs, const labels_t *l, __mpu_int64_t *v, char *err, size_t es ) +{ + __int128 x; + if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 ); + if( x < ( __int128 ) INT64_MIN || x > ( __int128 ) INT64_MAX ) return( 1 ); + *v = (__mpu_int64_t)x; + return( 0 ); +} + +static int +eval_u64( const char *s, equs_t *eqs, const labels_t *l, __mpu_uint64_t *v, char *err, size_t es ) +{ + __int128 x; + if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 ); + if( x < 0 || x > ( __int128 ) UINT64_MAX ) return( 1 ); + *v = (__mpu_uint64_t)x; + return( 0 ); +} + +static int +plain_integer_literal( const char *s ) +{ + const unsigned char *p = (const unsigned char *)s; + int base = 10, nd = 0; + while( isspace( *p ) ) p++; + if( *p == '+' || *p == '-' ) p++; + if( !*p ) return( 0 ); + if( p[0] == '0' && ( p[1] == 'x' || p[1] == 'X' ) ) + { + base = 16; + p += 2; + } + else + if( p[0] == '0' && ( p[1] == 'b' || p[1] == 'B' ) ) + { + base = 2; + p += 2; + } + else + if( p[0] == '0' && isdigit( p[1] ) ) + { + base = 8; + p++; + } + while( *p && !isspace( *p ) ) + { + int ok = ( base == 16 ) ? isxdigit( *p ) : ( base == 10 ) ? isdigit( *p ) : ( base == 8 ) ? ( *p >= '0' && *p <= '7' ) : ( *p == '0' || *p == '1' ); + if( !ok ) return( 0 ); + p++; + nd++; + } + while( isspace( *p ) ) p++; + return( nd && !*p ); +} + +static void +i128_decimal( __int128 v, char out[64] ) +{ + char tmp[64]; + int neg = v < 0, n = 0, i = 0; + unsigned __int128 u; + if( neg ) u = (unsigned __int128)( - ( v + 1 ) ) + 1; + else u = (unsigned __int128)v; + do + { + tmp[n++] = (char)( '0' + u % 10 ); + u /= 10; + } + while( u ); + if( neg ) out[i++] = '-'; + while( n ) out[i++] = tmp[--n]; + out[i] = 0; +} + +/* --------------------------- register syntax --------------------------- */ +static int +regparse( const char *s, cpu_register *r, int *isc, equs_t *eqs, char *err, size_t es ) +{ + const char *p = s, *at; + char *e; + unsigned long bits; + __mpu_uint64_t a; + while( isspace( (unsigned char)*p ) ) p++; + if( *p != 'r' && *p != 'R' && *p != 'c' && *p != 'C' ) return( -1 ); + *isc = ( *p == 'c' || *p == 'C' ); + p++; + errno = 0; + bits = strtoul( p, &e, 10 ); + if( errno || e == p || *e != '@' ) return( -1 ); + at = e + 1; + if( !*at ) return( -1 ); + if( eval_u64( at, eqs, 0, &a, err, es ) != 0 || a > UINT32_MAX ) return( -1 ); + r->bits = (unsigned int)bits; + r->address = (__mpu_uint32_t)a; + if( !__cpu_reg_valid( r->bits, r->address ) ) return( -1 ); + if( *isc && r->bits < 64 ) return( -1 ); + return( 0 ); +} + +static int +preg( buf_t *b, const char *s, int require_c, int require_r, equs_t *eqs, char *err, size_t es ) +{ + cpu_register r; + int c; + __mpu_UNIT_t x[3]; + if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) ) + { + snprintf( err, es, "bad register: %s", s ); + return( -1 ); + } + if( require_c && !c ) + { + snprintf( err, es, "complex instruction requires c<bits>@addr: %s", s ); + return( -1 ); + } + if( require_r && c ) + { + snprintf( err, es, "instruction requires r<bits>@addr: %s", s ); + return( -1 ); + } + if( __cpu_encode_reg24( x, &r ) ) return( -1 ); + return( put( b, x, 3 ) ); +} + +static int +preg_r64( buf_t *b, const char *s, const char *what, equs_t *eqs, char *err, size_t es ) +{ + cpu_register r; + int c; + __mpu_UNIT_t x[3]; + if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) || c || r.bits != 64 ) + { + snprintf( err, es, "%s requires r64@addr: %s", what, s ); + return( -1 ); + } + if( __cpu_encode_reg24( x, &r ) ) return( -1 ); + return( put( b, x, 3 ) ); +} + +static int +preg_ptr( buf_t *b, const char *s, equs_t *eqs, char *err, size_t es ) +{ + return( preg_r64( b, s, "PTR64 operand", eqs, err, es ) ); +} + +/* ---------------------------- string literals ---------------------------- */ +static int +hex_digit( int c ) +{ + if( c >= '0' && c <= '9' ) return( c - '0' ); + if( c >= 'a' && c <= 'f' ) return( c - 'a' + 10 ); + if( c >= 'A' && c <= 'F' ) return( c - 'A' + 10 ); + return( -1 ); +} + +static int +put_utf8_cp( buf_t *b, unsigned long cp, char *err, size_t es ) +{ + unsigned char x[4]; + size_t n; + if( cp == 0 ) + { + snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + return( -1 ); + } + if( cp <= 0x7f ) + { + x[0] = (unsigned char)cp; + n = 1; + } + else + if( cp <= 0x7ff ) + { + x[0] = 0xc0 | ( cp >> 6 ); + x[1] = 0x80 | ( cp & 0x3f ); + n = 2; + } + else + if( cp <= 0xffff ) + { + if( cp >= 0xd800 && cp <= 0xdfff ) + { + snprintf( err, es, "Unicode surrogate is not a valid scalar value" ); + return( -1 ); + } + x[0] = 0xe0 | ( cp >> 12 ); + x[1] = 0x80 | ( ( cp >> 6 ) & 0x3f ); + x[2] = 0x80 | ( cp & 0x3f ); + n = 3; + } + else + if( cp <= 0x10ffff ) + { + x[0] = 0xf0 | ( cp >> 18 ); + x[1] = 0x80 | ( ( cp >> 12 ) & 0x3f ); + x[2] = 0x80 | ( ( cp >> 6 ) & 0x3f ); + x[3] = 0x80 | ( cp & 0x3f ); + n = 4; + } + else + { + snprintf( err, es, "Unicode code point out of range" ); + return( -1 ); + } + if( put( b, x, n ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); +} + +static int +parse_utf8_string( const char *s, buf_t *out, char *err, size_t es ) +{ + const unsigned char *p = (const unsigned char *)s; + int closed = 0; + memset( out, 0, sizeof( *out ) ); + while( isspace( *p ) ) p++; + if( *p != '"' ) + { + snprintf( err, es, "expected quoted string" ); + return( -1 ); + } + p++; + while( *p ) + { + if( *p == '"' ) + { + p++; + closed = 1; + break; + } + if( *p != '\\' ) + { + if( *p == 0 ) + { + break; + } + if( put8( out, *p++ ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } continue; + } + p++; + if( !*p ) + { + snprintf( err, es, "unfinished escape sequence" ); + goto fail; + } + switch( *p++ ) + { + case '\\': if( put8( out, '\\' ) ) goto oom; + break; + case '"': if( put8( out, '"' ) ) goto oom; + break; + case 'n': if( put8( out, '\n' ) ) goto oom; + break; + case 'r': if( put8( out, '\r' ) ) goto oom; + break; + case 't': if( put8( out, '\t' ) ) goto oom; + break; + case 'b': if( put8( out, '\b' ) ) goto oom; + break; + case 'f': if( put8( out, '\f' ) ) goto oom; + break; + case 'v': if( put8( out, '\v' ) ) goto oom; + break; + case 'a': if( put8( out, '\a' ) ) goto oom; + break; + case '0': snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + goto fail; + case 'x': { + int h1, h2; + unsigned v; + if( !p[0] || !p[1] ) + { + snprintf( err, es, "\\x escape requires exactly two hex digits" ); + goto fail; + } + h1 = hex_digit( p[0] ); + h2 = hex_digit( p[1] ); + if( h1 < 0 || h2 < 0 ) + { + snprintf( err, es, "\\x escape requires exactly two hex digits" ); + goto fail; + } + v = (unsigned)( ( h1 << 4 ) | h2 ); + p += 2; + if( v == 0 ) + { + snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + goto fail; + } + if( put8( out, v ) ) goto oom; + break; + } + case 'u': + case 'U': { + int nd = p[-1] == 'u' ? 4 : 8, i, h; + unsigned long cp = 0; + for( i = 0; i < nd; i++ ) + { + if( !p[i] ) + { + snprintf( err, es, "Unicode escape requires %d hex digits", nd ); + goto fail; + } + h = hex_digit( p[i] ); + if( h < 0 ) + { + snprintf( err, es, "Unicode escape requires %d hex digits", nd ); + goto fail; + } + cp = ( cp << 4 ) | (unsigned)h; + } + p += nd; + if( put_utf8_cp( out, cp, err, es ) ) goto fail; + break; + } + default: snprintf( err, es, "unknown string escape sequence" ); + goto fail; + } + + } + if( !closed ) + { + snprintf( err, es, "unterminated string literal" ); + goto fail; + } + + while( isspace( *p ) ) p++; + if( *p ) + { + snprintf( err, es, "unexpected text after string literal" ); + goto fail; + } + + if( put8( out, 0 ) ) goto oom; + if( !mpu_utf8valid( (const __mpu_char8_t *)out->p ) ) + { + snprintf( err, es, "string literal is not valid UTF-8" ); + goto fail; + } + + return( 0 ); +oom: + snprintf( err, es, "out of memory" ); +fail: + free( out->p ); + memset( out, 0, sizeof( *out ) ); + return( -1 ); +} + +static int +real_literal_exact_value( const char *s, int nb, mpu_real *out ) +{ + mpu_wide_real_t src, narrow, back; + memset( src, 0, sizeof( src ) ); + memset( narrow, 0, sizeof( narrow ) ); + memset( back, 0, sizeof( back ) ); + if( ascii_to_real( ( mpu_real * ) src, (__mpu_char8_t *)s, NB_R65536 ) < 0 ) return( -1 ); + r_cvt( ( mpu_real * ) narrow, ( mpu_real * ) src, nb, NB_R65536 ); + r_cvt( ( mpu_real * ) back, ( mpu_real * ) narrow, NB_R65536, nb ); + if( r_cmp( ( mpu_real * ) src, ( mpu_real * ) back, NB_R65536 ) != 0 ) return( 0 ); + if( out ) memcpy( out, narrow, (size_t)nb ); + return( 1 ); +} + +static int +cimm_exact( const char *s, float *r, float *j, char *err, size_t es ) +{ + const char *rp = strstr( s, "r0" ), *jp; + char *rs = 0, *js = 0; + size_t rn, jn; + int ex; + if( !rp || rp == s ) return( -1 ); + jp = strstr( rp + 2, "j0" ); + if( !jp || jp == rp + 2 || jp[2] ) return( -1 ); + rn = (size_t)( rp - s ); + jn = (size_t)( jp - ( rp + 2 ) ); + rs = (char *)malloc( rn + 1 ); + js = (char *)malloc( jn + 1 ); + if( !rs || !js ) + { + free( rs ); + free( js ); + snprintf( err, es, "out of memory" ); + return( -2 ); + } + memcpy( rs, s, rn ); + rs[rn] = 0; + memcpy( js, rp + 2, jn ); + js[jn] = 0; + { + mpu_real32_t xr, xj; + ex = real_literal_exact_value( rs, NB_R32, ( mpu_real * ) xr ); + if( ex != 1 ) + { + free( rs ); + free( js ); + if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" ); + return( ex == 0 ? -2 : -1 ); + } + ex = real_literal_exact_value( js, NB_R32, ( mpu_real * ) xj ); + if( ex != 1 ) + { + free( rs ); + free( js ); + if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" ); + return( ex == 0 ? -2 : -1 ); + } + memcpy( r, xr, NB_R32 ); + memcpy( j, xj, NB_R32 ); + } + free( rs ); + free( js ); + return( 0 ); +} + +static int +constid( const char *s, unsigned *v ) +{ + struct K + { + const char *n; + unsigned v; + } + ks[] = + { + { "ZERO", CPU_RCONST_ZERO }, + { "HALF", CPU_RCONST_HALF }, + { "ONE", CPU_RCONST_ONE }, + { "TWO", CPU_RCONST_TWO }, + { "TEN", CPU_RCONST_TEN }, + { "MTEN", CPU_RCONST_MTEN }, + { "32", CPU_RCONST_32 }, + { "PI", CPU_RCONST_PI }, + { "E", CPU_RCONST_E }, + { "1_LN2", CPU_RCONST_1_LN2 }, + { "LN2", CPU_RCONST_LN2 }, + { "1_LN10", CPU_RCONST_1_LN10 }, + { "LN10", CPU_RCONST_LN10 }, + { "1_LG2", CPU_RCONST_1_LG2 }, + { "LG2", CPU_RCONST_LG2 }, + { "PI_2", CPU_RCONST_PI_2 }, + { "PI_3", CPU_RCONST_PI_3 }, + { "PI_4", CPU_RCONST_PI_4 }, + { "PI_5", CPU_RCONST_PI_5 }, + { "PI_6", CPU_RCONST_PI_6 }, + { "1_PI", CPU_RCONST_1_PI }, + { "2_PI", CPU_RCONST_2_PI }, + { "3_PI", CPU_RCONST_3_PI }, + { "4_PI", CPU_RCONST_4_PI }, + { "5_PI", CPU_RCONST_5_PI }, + { "2PI", CPU_RCONST_2PI }, + { "3PI", CPU_RCONST_3PI }, + { "1_2PI", CPU_RCONST_1_2PI }, + { "1_3PI", CPU_RCONST_1_3PI }, + { "1_4PI", CPU_RCONST_1_4PI }, + { "1_5PI", CPU_RCONST_1_5PI }, + { "1_6PI", CPU_RCONST_1_6PI }, + { "3PI_4", CPU_RCONST_3PI_4 }, + { "SQRTPI", CPU_RCONST_SQRTPI }, + { "1_SQRTPI", CPU_RCONST_1_SQRTPI }, + { "2_SQRTPI", CPU_RCONST_2_SQRTPI }, + { "SQRT2", CPU_RCONST_SQRT2 }, + { "1_SQRT2", CPU_RCONST_1_SQRT2 }, + { "SQRT3", CPU_RCONST_SQRT3 }, + { "1_SQRT3", CPU_RCONST_1_SQRT3 }, + { "DEGREE", CPU_RCONST_DEGREE }, + { "1_DEGREE", CPU_RCONST_1_DEGREE }, + { "GOLDENRATIO", CPU_RCONST_GOLDENRATIO }, + { "EULERGAMMA", CPU_RCONST_EULERGAMMA }, + { "CATALAN", CPU_RCONST_CATALAN }, + { "EPSILON", CPU_RCONST_EPSILON }, + { "IND", CPU_RCONST_IND }, + { "PNAN", CPU_RCONST_PNAN }, + { "NNAN", CPU_RCONST_NNAN }, + { "PNANMAX", CPU_RCONST_PNANMAX }, + { "NNANMAX", CPU_RCONST_NNANMAX }, + { "PNANMIN", CPU_RCONST_PNANMIN }, + { "NNANMIN", CPU_RCONST_NNANMIN }, + { "PINF", CPU_RCONST_PINF }, + { "NINF", CPU_RCONST_NINF }, + { "PREALMIN", CPU_RCONST_PREALMIN }, + { "NREALMIN", CPU_RCONST_NREALMIN }, + { "PREALMAX", CPU_RCONST_PREALMAX }, + { "NREALMAX", CPU_RCONST_NREALMAX }, + { "PZERO", CPU_RCONST_PZERO }, + { "NZERO", CPU_RCONST_NZERO } + }; + size_t i; + for( i = 0; i < sizeof( ks ) / sizeof( ks[0] ); i++ ) if( streqi( s, ks[i].n ) ) + { + *v = ks[i].v; + return( 0 ); + } + return( -1 ); +} + +static int +pswbitid( const char *s, unsigned *v ) +{ + struct B + { + const char *n; + unsigned v; + } + bs[] = + { + { "AF", CPU_PSWBIT_AF }, + { "CF", CPU_PSWBIT_CF }, + { "OF", CPU_PSWBIT_OF }, + { "SF", CPU_PSWBIT_SF }, + { "PF", CPU_PSWBIT_PF }, + { "ZF", CPU_PSWBIT_ZF }, + { "RF", CPU_PSWBIT_RF }, + { "VF", CPU_PSWBIT_VF }, + { "DOMF", CPU_PSWBIT_DOMF }, + { "SNGF", CPU_PSWBIT_SNGF }, + { "OVFF", CPU_PSWBIT_OVFF }, + { "UDFF", CPU_PSWBIT_UDFF }, + { "TLSF", CPU_PSWBIT_TLSF }, + { "PLSF", CPU_PSWBIT_PLSF }, + { "INDF", CPU_PSWBIT_INDF }, + { "INXF", CPU_PSWBIT_INXF }, + { "HF", CPU_PSWBIT_HF }, + { "TF", CPU_PSWBIT_TF }, + { "IF", CPU_PSWBIT_IF } + }; + size_t i; + for( i = 0; i < sizeof( bs ) / sizeof( bs[0] ); i++ ) + if( streqi( s, bs[i].n ) ) + { + *v = bs[i].v; + return( 0 ); + } + return( -1 ); +} + +typedef struct +{ + const char *n; + unsigned op; + int nr; + int c; + int r; +} optab_t; +static const optab_t ops[] = +{ + { "NOP", CPU_OP_NOP, 0, 0, 0 }, + { "HALT", CPU_OP_HALT, 0, 0, 0 }, + { "END", CPU_OP_END, 0, 0, 0 }, + { "SYSCALL", CPU_OP_SYSCALL, 0, 0, 0 }, + { "CLRCPUF", CPU_OP_CLRCPUF, 0, 0, 0 }, + { "CLRMPUF", CPU_OP_CLRMPUF, 0, 0, 0 }, + { "CLR", CPU_OP_CLR, 1, 0, 0 }, + { "MOV", CPU_OP_MOV, 2, 0, 0 }, + { "SWAP", CPU_OP_SWAP, 2, 0, 0 }, + { "BSWAP", CPU_OP_BSWAP, 2, 0, 1 }, + { "PUSH", CPU_OP_PUSH, 1, 0, 0 }, + { "POP", CPU_OP_POP, 1, 0, 0 }, + { "PUSHFP", CPU_OP_PUSHFP, 0, 0, 0 }, + { "SETFP", CPU_OP_SETFP, 0, 0, 0 }, + { "RESTSP", CPU_OP_RESTSP, 0, 0, 0 }, + { "POPFP", CPU_OP_POPFP, 0, 0, 0 }, + { "IADD", CPU_OP_IADD, 3, 0, 1 }, + { "IADC", CPU_OP_IADC, 3, 0, 1 }, + { "ISUB", CPU_OP_ISUB, 3, 0, 1 }, + { "ISBB", CPU_OP_ISBB, 3, 0, 1 }, + { "IAND", CPU_OP_IAND, 3, 0, 1 }, + { "IOR", CPU_OP_IOR, 3, 0, 1 }, + { "IXOR", CPU_OP_IXOR, 3, 0, 1 }, + { "INOT", CPU_OP_INOT, 2, 0, 1 }, + { "INEG", CPU_OP_INEG, 2, 0, 1 }, + { "ICMP", CPU_OP_ICMP, 2, 0, 1 }, + { "ITEST", CPU_OP_ITEST, 2, 0, 1 }, + { "ISHL", CPU_OP_ISHL, 2, 0, 1 }, + { "ISHR", CPU_OP_ISHR, 2, 0, 1 }, + { "ISAL", CPU_OP_ISAL, 2, 0, 1 }, + { "ISAR", CPU_OP_ISAR, 2, 0, 1 }, + { "IROL", CPU_OP_IROL, 2, 0, 1 }, + { "IROR", CPU_OP_IROR, 2, 0, 1 }, + { "IRCL", CPU_OP_IRCL, 2, 0, 1 }, + { "IRCR", CPU_OP_IRCR, 2, 0, 1 }, + { "IINC", CPU_OP_IINC, 2, 0, 1 }, + { "IDEC", CPU_OP_IDEC, 2, 0, 1 }, + { "IXCHG", CPU_OP_IXCHG, 2, 0, 1 }, + { "ICPY", CPU_OP_ICPY, 2, 0, 1 }, + { "ICVT", CPU_OP_ICVT, 2, 0, 1 }, + { "IMUL", CPU_OP_IMUL, 3, 0, 1 }, + { "ISMUL", CPU_OP_ISMUL, 3, 0, 1 }, + { "IDIV", CPU_OP_IDIV, 4, 0, 1 }, + { "ISDIV", CPU_OP_ISDIV, 4, 0, 1 }, + { "IBITSLEN", CPU_OP_IBITSLEN, 2, 0, 1 }, + { "RADD", CPU_OP_RADD, 3, 0, 1 }, + { "RSUB", CPU_OP_RSUB, 3, 0, 1 }, + { "RMUL", CPU_OP_RMUL, 3, 0, 1 }, + { "RDIV", CPU_OP_RDIV, 3, 0, 1 }, + { "RSQRT", CPU_OP_RSQRT, 2, 0, 1 }, + { "RSIN", CPU_OP_RSIN, 2, 0, 1 }, + { "RCOS", CPU_OP_RCOS, 2, 0, 1 }, + { "RTAN", CPU_OP_RTAN, 2, 0, 1 }, + { "RASIN", CPU_OP_RASIN, 2, 0, 1 }, + { "RACOS", CPU_OP_RACOS, 2, 0, 1 }, + { "RATAN", CPU_OP_RATAN, 2, 0, 1 }, + { "RLOG", CPU_OP_RLOG, 2, 0, 1 }, + { "RLOG10", CPU_OP_RLOG10, 2, 0, 1 }, + { "RLOG2", CPU_OP_RLOG2, 2, 0, 1 }, + { "REXP", CPU_OP_REXP, 2, 0, 1 }, + { "RSINH", CPU_OP_RSINH, 2, 0, 1 }, + { "RCOSH", CPU_OP_RCOSH, 2, 0, 1 }, + { "RTANH", CPU_OP_RTANH, 2, 0, 1 }, + { "RPOW", CPU_OP_RPOW, 3, 0, 1 }, + { "RHYPOT", CPU_OP_RHYPOT, 3, 0, 1 }, + { "RATAN2", CPU_OP_RATAN2, 3, 0, 1 }, + { "RNEG", CPU_OP_RNEG, 2, 0, 1 }, + { "RABS", CPU_OP_RABS, 2, 0, 1 }, + { "RCPY", CPU_OP_RCPY, 2, 0, 1 }, + { "RCVT", CPU_OP_RCVT, 2, 0, 1 }, + { "RFLOOR", CPU_OP_RFLOOR, 2, 0, 1 }, + { "RCEIL", CPU_OP_RCEIL, 2, 0, 1 }, + { "RROUND", CPU_OP_RROUND, 2, 0, 1 }, + { "RFREXP", CPU_OP_RFREXP, 3, 0, 1 }, + { "RLDEXP", CPU_OP_RLDEXP, 3, 0, 1 }, + { "RLOGB", CPU_OP_RLOGB, 2, 0, 1 }, + { "RMODF", CPU_OP_RMODF, 3, 0, 1 }, + { "RREMAIN", CPU_OP_RREMAIN, 3, 0, 1 }, + { "RLOG1P", CPU_OP_RLOG1P, 2, 0, 1 }, + { "REXPM1", CPU_OP_REXPM1, 2, 0, 1 }, + { "RASINH", CPU_OP_RASINH, 2, 0, 1 }, + { "RACOSH", CPU_OP_RACOSH, 2, 0, 1 }, + { "RATANH", CPU_OP_RATANH, 2, 0, 1 }, + { "ITOR", CPU_OP_ITOR, 2, 0, 1 }, + { "UITOR", CPU_OP_UITOR, 2, 0, 1 }, + { "RTOI", CPU_OP_RTOI, 3, 0, 1 }, + { "RTOUI", CPU_OP_RTOUI, 3, 0, 1 }, + { "CADD", CPU_OP_CADD, 3, 1, 0 }, + { "CSUB", CPU_OP_CSUB, 3, 1, 0 }, + { "CMUL", CPU_OP_CMUL, 3, 1, 0 }, + { "CDIV", CPU_OP_CDIV, 3, 1, 0 }, + { "CEXP", CPU_OP_CEXP, 2, 1, 0 }, + { "CSIN", CPU_OP_CSIN, 2, 1, 0 }, + { "CCOS", CPU_OP_CCOS, 2, 1, 0 }, + { "CSINH", CPU_OP_CSINH, 2, 1, 0 }, + { "CCOSH", CPU_OP_CCOSH, 2, 1, 0 }, + { "CLOG", CPU_OP_CLOG, 2, 1, 0 }, + { "CSQRT", CPU_OP_CSQRT, 2, 1, 0 }, + { "CPOW", CPU_OP_CPOW, 3, 1, 0 }, + { "CCONJ", CPU_OP_CCONJ, 2, 1, 0 }, + { "CCPY", CPU_OP_CCPY, 2, 1, 0 }, + { "CCVT", CPU_OP_CCVT, 2, 1, 0 } +}; + +static int +emit_instruction( buf_t *c, fixups_t *df, fixups_t *cf, fixups_t *pf, equs_t *eqs, char *mn, char *rest, char *err, size_t es ) +{ + char *av[4]; + int ac = rest && *trim( rest ) ? splitargs( rest, av, 4 ) : 0; + size_t i; + unsigned k; + for( i = 0; i < sizeof( ops ) / sizeof( ops[0] ); i++ ) if( streqi( mn, ops[i].n ) ) + { + buf_t tmp = + { + 0 + }; + if( ac != ops[i].nr ) + { + snprintf( err, es, "wrong operand count for %s", mn ); + return( -1 ); + } + if( put16( &tmp, ops[i].op ) ) return( -1 ); + for( k = 0; k < (unsigned)ac; k++ ) if( preg( &tmp, av[k], ops[i].c, ops[i].r, eqs, err, es ) ) + { + free( tmp.p ); + return( -1 ); + } + if( put( c, tmp.p, tmp.n ) ) + { + free( tmp.p ); + return( -1 ); + } + free( tmp.p ); + return( 0 ); + } + if( streqi( mn, "SETF" ) || streqi( mn, "CLRF" ) ) + { + unsigned bit; + if( ac != 1 || pswbitid( av[0], &bit ) || bit > CPU_PSWBIT_WRITE_MAX ) + { + snprintf( err, es, "%s requires one writable PSW flag in range AF..INXF", mn ); + return( -1 ); + } + if( put16( c, streqi( mn, "SETF" ) ? CPU_OP_SETF : CPU_OP_CLRF ) || + put8( c, bit ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "EXIT" ) ) + { + cpu_register r; + int ic; + if( ac != 1 || regparse( av[0], &r, &ic, eqs, err, es ) || ic || r.bits != 32 ) + { + snprintf( err, es, "EXIT requires one r32 status operand" ); + return( -1 ); + } + if( put16( c, CPU_OP_EXIT ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LDR" ) ) + { + __mpu_real64_t v; + mpu_real64_t xv; + __mpu_uint64_t u; + cpu_register r; + int ic, ex, nb; + if( ac != 2 || regparse( av[0], &r, &ic, eqs, err, es ) || ic ) + { + snprintf( err, es, "bad LDR destination" ); + return( -1 ); + } + if( r.bits < 32 ) + { + snprintf( err, es, "LDR requires r32 or wider" ); + return( -1 ); + } + nb = ( r.bits == 32 ) ? NB_R32 : NB_R64; + if( nb == NB_R32 ) + { + mpu_real32_t x32; + ex = real_literal_exact_value( av[1], NB_R32, ( mpu_real * ) x32 ); + if( ex == 1 ) r_cvt( ( mpu_real * ) xv, ( mpu_real * ) x32, NB_R64, NB_R32 ); + } + else ex = real_literal_exact_value( av[1], NB_R64, ( mpu_real * ) xv ); + if( ex < 0 ) + { + snprintf( err, es, "bad real immediate" ); + return( -1 ); + } + if( !ex ) + { + snprintf( err, es, "LDR immediate is not exactly representable as real%d;\nuse .real <bits>, value in DATA and LOAD", r.bits == 32 ? 32 : 64 ); + return( -1 ); + } + memcpy( &v, xv, NB_R64 ); + if( put16( c, CPU_OP_LDR ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 ); + memcpy( &u, &v, 8 ); + return( put64( c, u ) ); + } + if( streqi( mn, "LDC" ) ) + { + float r, j; + __mpu_uint32_t ur, ui; + __mpu_uint64_t u; + cpu_register rg; + int ic, cr; + if( ac != 2 || regparse( av[0], &rg, &ic, eqs, err, es ) || !ic ) + { + snprintf( err, es, "bad LDC destination" ); + return( -1 ); + } + cr = cimm_exact( av[1], &r, &j, err, es ); + if( cr == -1 ) + { + snprintf( err, es, "bad complex immediate" ); + return( -1 ); + } + if( cr == -2 ) return( -1 ); + memcpy( &ur, &r, 4 ); + memcpy( &ui, &j, 4 ); + u = ( (__mpu_uint64_t)ur << 32 ) | ui; + if( put16( c, CPU_OP_LDC ) || preg( c, av[0], 1, 0, eqs, err, es ) || put64( c, u ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RCONST" ) ) + { + if( ac != 2 || constid( av[1], &k ) ) + { + snprintf( err, es, "bad RCONST" ); + return( -1 ); + } + if( put16( c, CPU_OP_RCONST ) || preg( c, av[0], 0, 1, eqs, err, es ) || put8( c, k ) ) return( -1 ); + return( 0 ); + } + + if( streqi( mn, "ISHLN" ) || streqi( mn, "ISHRN" ) || streqi( mn, "ISALN" ) || streqi( mn, "ISARN" ) || + streqi( mn, "IROLN" ) || streqi( mn, "IRORN" ) || streqi( mn, "IRCLN" ) || streqi( mn, "IRCRN" )) + { + unsigned op; + if( ac != 3 ) + { + snprintf( err, es, "%s requires dst, src, r64 count", mn ); + return( -1 ); + } + if( streqi( mn, "ISHLN" ) ) op = CPU_OP_ISHLN; + else if( streqi( mn, "ISHRN" ) ) op = CPU_OP_ISHRN; + else if( streqi( mn, "ISALN" ) ) op = CPU_OP_ISALN; + else if( streqi( mn, "ISARN" ) ) op = CPU_OP_ISARN; + else if( streqi( mn, "IROLN" ) ) op = CPU_OP_IROLN; + else if( streqi( mn, "IRORN" ) ) op = CPU_OP_IRORN; + else if( streqi( mn, "IRCLN" ) ) op = CPU_OP_IRCLN; + else op = CPU_OP_IRCRN; + if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) || + preg_r64( c, av[2], "shift count", eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RCMP" ) ) + { + if( ac != 3 ) + { + snprintf( err, es, "RCMP requires r64 result, real a, real b" ); + return( -1 ); + } + if( put16( c, CPU_OP_RCMP ) || preg_r64( c, av[0], "RCMP result", eqs, err, es ) || + preg( c, av[1], 0, 1, eqs, err, es ) || preg( c, av[2], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RISNEG" ) || streqi( mn, "RSIGN" ) || streqi( mn, "RISIND" ) || + streqi( mn, "RISNAN" ) || streqi( mn, "RISNANMAX" ) || streqi( mn, "RISNANMIN" ) || + streqi( mn, "RISINF" ) || streqi( mn, "RISSIGNULL" )) + { + unsigned op; + if( ac != 2 ) + { + snprintf( err, es, "%s requires r64 result, real source", mn ); + return( -1 ); + } + if( streqi( mn, "RISNEG" ) ) op = CPU_OP_RISNEG; + else if( streqi( mn, "RSIGN" ) ) op = CPU_OP_RSIGN; + else if( streqi( mn, "RISIND" ) ) op = CPU_OP_RISIND; + else if( streqi( mn, "RISNAN" ) ) op = CPU_OP_RISNAN; + else if( streqi( mn, "RISNANMAX" ) ) op = CPU_OP_RISNANMAX; + else if( streqi( mn, "RISNANMIN" ) ) op = CPU_OP_RISNANMIN; + else if( streqi( mn, "RISINF" ) ) op = CPU_OP_RISINF; + else op = CPU_OP_RISSIGNULL; + if( put16( c, op ) || preg_r64( c, av[0], "real predicate result", eqs, err, es ) || + preg( c, av[1], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CREAL" ) || streqi( mn, "CIMAG" ) || streqi( mn, "CABS" ) || + streqi( mn, "CNORM" ) || streqi( mn, "CARG" )) + { + unsigned op; + if( ac != 2 ) + { + snprintf( err, es, "%s requires real destination, complex source", mn ); + return( -1 ); + } + if( streqi( mn, "CREAL" ) ) op = CPU_OP_CREAL; + else if( streqi( mn, "CIMAG" ) ) op = CPU_OP_CIMAG; + else if( streqi( mn, "CABS" ) ) op = CPU_OP_CABS; + else if( streqi( mn, "CNORM" ) ) op = CPU_OP_CNORM; + else op = CPU_OP_CARG; + if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 1, 0, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CRTOC" ) ) + { + if( ac != 2 ) + { + snprintf( err, es, "CRTOC requires complex destination, real source" ); + return( -1 ); + } + if( put16( c, CPU_OP_CRTOC ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CGEN" ) || streqi( mn, "CPOLAR" ) ) + { + unsigned op = streqi( mn, "CGEN" ) ? CPU_OP_CGEN : CPU_OP_CPOLAR; + if( ac != 3 ) + { + snprintf( err, es, "%s requires complex destination, two real sources", mn ); + return( -1 ); + } + if( put16( c, op ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) || + preg( c, av[2], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CISEQ" ) || streqi( mn, "CISNE" ) ) + { + unsigned op = streqi( mn, "CISEQ" ) ? CPU_OP_CISEQ : CPU_OP_CISNE; + if( ac != 3 ) + { + snprintf( err, es, "%s requires r64 result, complex a, complex b", mn ); + return( -1 ); + } + if( put16( c, op ) || preg_r64( c, av[0], "complex predicate result", eqs, err, es ) || + preg( c, av[1], 1, 0, eqs, err, es ) || preg( c, av[2], 1, 0, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LOAD" ) || streqi( mn, "STORE" ) ) + { + int store = streqi( mn, "STORE" ); + if( ac != 2 ) + { + snprintf( err, es, "wrong operand count" ); + return( -1 ); + } + if( put16( c, store ? CPU_OP_STORE : CPU_OP_LOAD ) ) return( -1 ); + if( store ) + { + if( addfix( df, c->n, av[0] ) || put64( c, 0 ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || addfix( df, c->n, av[1] ) || put64( c, 0 ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "LEA" ) ) + { + if( ac != 2 ) + { + snprintf( err, es, "LEA requires r64 destination and pointer expression" ); + return( -1 ); + } + if( put16( c, CPU_OP_LEA ) || preg_ptr( c, av[0], eqs, err, es ) || addfix( pf, c->n, av[1] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LEASP" ) ) + { + __mpu_int64_t off; + if( ac != 2 ) + { + snprintf( err, es, "LEASP requires r64 destination and signed SP offset" ); + return( -1 ); + } + { + int er = eval_i64( av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "LEASP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LEASP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LEAFP" ) ) + { + __mpu_int64_t off; + if( ac != 2 ) + { + snprintf( err, es, "LEAFP requires r64 destination and signed FP offset" ); + return( -1 ); + } + { + int er = eval_i64( av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "LEAFP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LEAFP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LOADP" ) || streqi( mn, "STOREP" ) ) + { + int store = streqi( mn, "STOREP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two register operands", mn ); + return( -1 ); + } + if( put16( c, store ? CPU_OP_STOREP : CPU_OP_LOADP ) ) return( -1 ); + if( store ) + { + if( preg_ptr( c, av[0], eqs, err, es ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || preg_ptr( c, av[1], eqs, err, es ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "PADD" ) || streqi( mn, "PSUB" ) || streqi( mn, "PDIFF" ) ) + { + unsigned op = streqi( mn, "PADD" ) ? CPU_OP_PADD : ( streqi( mn, "PSUB" ) ? CPU_OP_PSUB : CPU_OP_PDIFF ); + if( ac != 3 ) + { + snprintf( err, es, "%s requires three r64 operands", mn ); + return( -1 ); + } + if( put16( c, op ) || + preg_r64( c, av[0], mn, eqs, err, es ) || + preg_r64( c, av[1], mn, eqs, err, es ) || + preg_r64( c, av[2], mn, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "PADDI" ) || streqi( mn, "PSUBI" ) ) + { + __mpu_int64_t off; + unsigned op = streqi( mn, "PADDI" ) ? CPU_OP_PADDI : CPU_OP_PSUBI; + if( ac != 3 ) + { + snprintf( err, es, "%s requires r64 destination, r64 PTR64 source and signed OFF64", mn ); + return( -1 ); + } + { + int er = eval_i64( av[2], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, op ) || + preg_r64( c, av[0], mn, eqs, err, es ) || + preg_r64( c, av[1], mn, eqs, err, es ) || + put64( c, (__mpu_uint64_t)off )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "BMOV" ) ) + { + if( ac != 3 ) + { + snprintf( err, es, "BMOV requires r64 destination PTR64, r64 source PTR64 and r64 byte count" ); + return( -1 ); + } + if( put16( c, CPU_OP_BMOV ) || + preg_ptr( c, av[0], eqs, err, es ) || + preg_ptr( c, av[1], eqs, err, es ) || + preg_r64( c, av[2], "BMOV byte count", eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "BMOVI" ) ) + { + __mpu_uint64_t count; + if( ac != 3 ) + { + snprintf( err, es, "BMOVI requires r64 destination PTR64, r64 source PTR64 and unsigned IMM64 byte count" ); + return( -1 ); + } + { + int er = eval_u64( av[2], eqs, 0, &count, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "BMOVI byte count must fit unsigned 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_BMOVI ) || + preg_ptr( c, av[0], eqs, err, es ) || + preg_ptr( c, av[1], eqs, err, es ) || + put64( c, count )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "JMPP" ) || streqi( mn, "CALLP" ) ) + { + unsigned op = streqi( mn, "CALLP" ) ? CPU_OP_CALLP : CPU_OP_JMPP; + if( ac != 1 ) + { + snprintf( err, es, "%s requires one r64 PROGRAM PTR64 operand", mn ); + return( -1 ); + } + if( put16( c, op ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "JMP" ) || streqi( mn, "CALL" ) ) + { + int call = streqi( mn, "CALL" ); + if( ac != 1 ) + { + snprintf( err, es, "%s requires one CODE label", mn ); + return( -1 ); + } + if( put16( c, call ? CPU_OP_CALL : CPU_OP_JMP ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RET" ) ) + { + if( ac != 0 ) + { + snprintf( err, es, "RET takes no operands" ); + return( -1 ); + } + return( put16( c, CPU_OP_RET ) ); + } + if( streqi( mn, "JSET" ) || streqi( mn, "JCLR" ) ) + { + unsigned bit; + if( ac != 2 || pswbitid( av[0], &bit ) ) + { + snprintf( err, es, "bad PSW bit for %s", mn ); + return( -1 ); + } + if( put16( c, streqi( mn, "JSET" ) ? CPU_OP_JSET : CPU_OP_JCLR ) || + put8( c, bit ) || addfix( cf, c->n, av[1] ) || put64( c, 0 )) return( -1 ); + return( 0 ); + } + { + struct A + { + const char *n; + unsigned op; + unsigned bit; + } + as[] = + { + { "JZ", CPU_OP_JSET, CPU_PSWBIT_ZF }, + { "JNZ", CPU_OP_JCLR, CPU_PSWBIT_ZF }, + { "JC", CPU_OP_JSET, CPU_PSWBIT_CF }, + { "JNC", CPU_OP_JCLR, CPU_PSWBIT_CF }, + { "JO", CPU_OP_JSET, CPU_PSWBIT_OF }, + { "JNO", CPU_OP_JCLR, CPU_PSWBIT_OF }, + { "JS", CPU_OP_JSET, CPU_PSWBIT_SF }, + { "JNS", CPU_OP_JCLR, CPU_PSWBIT_SF } + }; + size_t ai; + for( ai = 0; ai < sizeof( as ) / sizeof( as[0] ); ai++ ) if( streqi( mn, as[ai].n ) ) + { + if( ac != 1 ) + { + snprintf( err, es, "%s requires one CODE label", mn ); + return( -1 ); + } + if( put16( c, as[ai].op ) || put8( c, as[ai].bit ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + } + if( streqi( mn, "LDSP" ) || streqi( mn, "STSP" ) ) + { + __mpu_int64_t off; + int store = streqi( mn, "STSP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two operands", mn ); + return( -1 ); + } + { + int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, store ? CPU_OP_STSP : CPU_OP_LDSP ) ) return( -1 ); + if( store ) + { + if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "LDFP" ) || streqi( mn, "STFP" ) ) + { + __mpu_int64_t off; + int store = streqi( mn, "STFP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two operands", mn ); + return( -1 ); + } + { + int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, store ? CPU_OP_STFP : CPU_OP_LDFP ) ) return( -1 ); + if( store ) + { + if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "ADJSP" ) ) + { + __mpu_int64_t off; + if( ac != 1 ) + { + snprintf( err, es, "ADJSP requires one signed byte offset" ); + return( -1 ); + } + { + int er = eval_i64( av[0], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "ADJSP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_ADJSP ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "SETSP" ) ) + { + if( ac != 1 ) + { + snprintf( err, es, "SETSP requires one r64 PTR64 operand" ); + return( -1 ); + } + if( put16( c, CPU_OP_SETSP ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 ); + return( 0 ); + } + snprintf( err, es, "unknown mnemonic: %s", mn ); + return( -1 ); +} + +/* Special LDI emitter kept separate to make signed 64-bit parsing explicit. */ +static int +emit_ldi( buf_t *c, equs_t *eqs, char *rest, char *err, size_t es ) +{ + char *av[3]; + __mpu_int64_t v; + __int128 xv; + cpu_register r; + int ic, ac = splitargs( rest, av, 3 ); + if( ac != 2 ) + { + snprintf( err, es, "wrong operand count for LDI" ); + return( -1 ); + } + if( regparse( av[0], &r, &ic, eqs, err, es ) || ic ) + { + snprintf( err, es, "bad LDI destination" ); + return( -1 ); + } + if( eval_expression( av[1], eqs, 0, &xv, err, es ) ) + { + snprintf( err, es, "bad integer immediate: %s", av[1] ); + return( -1 ); + } + if( xv < ( __int128 ) INT64_MIN || xv > ( __int128 ) INT64_MAX ) + { + snprintf( err, es, "LDI immediate is not exactly representable as integer64;\nuse .integer <bits>, value in DATA and LOAD" ); + return( -1 ); + } + v = (__mpu_int64_t)xv; + if( r.bits < 64 ) + { + __mpu_int64_t lo = - ( (__mpu_int64_t)1 << ( r.bits - 1 ) ), hi = ( (__mpu_int64_t)1 << ( r.bits - 1 ) ) - 1; + if( v < lo || v > hi ) + { + snprintf( err, es, "LDI immediate is not exactly representable as integer%u;\nuse .integer <bits>, value in DATA and LOAD", r.bits ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LDI ) || preg( c, av[0], 0, 1, eqs, err, es ) || put64( c, (__mpu_uint64_t)v ) ) return( -1 ); + return( 0 ); +} + +static int datadir( buf_t *d, char *line, fixups_t *cptr_fix, fixups_t *ptr_fix, + __mpu_uint64_t *section_align, equs_t *eqs, char *err, size_t es) +{ + char *p = trim( line ), *mn = p, *rest, *av[256]; + int ac, bits, nb; + __mpu_uint64_t uv; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + + if( streqi( mn, ".align" ) ) + { + if( !*rest ) + { + snprintf( err, es, ".align requires a value" ); + return( -1 ); + } + { + int er = eval_u64( rest, eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || !uv || uv > (__mpu_uint64_t)SIZE_MAX ) + { + snprintf( err, es, "bad .align" ); + return( -1 ); + } + } + if( section_align && uv > *section_align ) * section_align = uv; + while( d->n % (size_t)uv ) if( put8( d, 0 ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".utf8" ) || streqi( mn, ".ucs2" ) ) + { + buf_t text = + { + 0 + }; + if( !*rest ) + { + snprintf( err, es, "%s requires a quoted UTF-8 string", mn ); + return( -1 ); + } + if( parse_utf8_string( rest, &text, err, es ) ) return( -1 ); + if( streqi( mn, ".utf8" ) ) + { + if( put( d, text.p, text.n ) ) + { + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + else + { + __mpu_size_t nch = mpu_utf8_to_ucs2( 0, (const __mpu_char8_t *)text.p, 0 ), i; + __mpu_char16_t *ucs; + if( nch == (__mpu_size_t)-1 ) + { + free( text.p ); + snprintf( err, es, ".ucs2 string is not representable as strict UCS-2" ); + return( -1 ); + } + ucs = (__mpu_char16_t *)calloc( (size_t)nch + 1, sizeof( *ucs ) ); + if( !ucs ) + { + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( mpu_utf8_to_ucs2( ucs, (const __mpu_char8_t *)text.p, nch + 1 ) != nch ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "UTF-8 to UCS-2 conversion failed" ); + return( -1 ); + } + for( i = 0; i < nch; i++ ) + { + __mpu_UNIT_t x[2]; + __cpu_data_put_u16( x, (__mpu_uint16_t)ucs[i] ); + if( put( d, x, 2 ) ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + { + __mpu_UNIT_t z[2]; + __cpu_data_put_u16( z, 0 ); + if( put( d, z, 2 ) ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + free( ucs ); + } + free( text.p ); + return( 0 ); + } + + if( !*rest ) + { + snprintf( err, es, "data directive requires operands" ); + return( -1 ); + } + ac = splitargs( rest, av, 256 ); + if( ac < 0 ) + { + snprintf( err, es, "too many or malformed directive arguments" ); + return( -1 ); + } + + if( streqi( mn, ".byte" ) ) + { + int i; + if( ac < 1 ) + { + snprintf( err, es, ".byte requires one or more values" ); + return( -1 ); + } + for( i = 0; i < ac; i++ ) + { + int er = eval_u64( av[i], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 255 ) + { + snprintf( err, es, ".byte value must fit 8 bits: %s", av[i] ); + return( -1 ); + } + if( put8( d, (unsigned)uv ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + return( 0 ); + } + + if( streqi( mn, ".integer" ) || streqi( mn, ".int" ) ) + { + int i; + mpu_wide_int_t x; + if( ac < 2 ) + { + snprintf( err, es, "%s requires: bits, value[, value...]", mn ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .integer width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 8 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .integer width" ); + return( -1 ); + } + for( i = 1; i < ac; i++ ) + { + const char *value = av[i]; + char dec[64]; + __int128 ev; + memset( x, 0, sizeof( x ) ); + if( !plain_integer_literal( value ) ) + { + if( eval_expression( value, eqs, 0, &ev, err, es ) ) return( -1 ); + i128_decimal( ev, dec ); + value = dec; + } + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)value, nb ); + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + return( 0 ); + } + + if( streqi( mn, ".real" ) ) + { + mpu_wide_real_t x; + if( ac != 2 ) + { + snprintf( err, es, ".real requires: bits, value" ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .real width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 32 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .real width" ); + return( -1 ); + } + memset( x, 0, sizeof( x ) ); + if( ascii_to_real( ( mpu_real * ) x, (__mpu_char8_t *)av[1], nb ) < 0 ) + { + snprintf( err, es, "bad .real constant" ); + return( -1 ); + } + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".complex" ) ) + { + mpu_wide_complex_t x; + mpu_wide_real_t r, j; + if( ac != 3 ) + { + snprintf( err, es, ".complex requires: physical-bits, real, imag" ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .complex physical width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 64 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .complex physical width" ); + return( -1 ); + } + if( ascii_to_real( ( mpu_real * ) r, (__mpu_char8_t *)av[1], nb / 2 ) < 0 || ascii_to_real( ( mpu_real * ) j, (__mpu_char8_t *)av[2], nb / 2 ) < 0 ) + { + snprintf( err, es, "bad .complex component" ); + return( -1 ); + } + c_gen_complex( ( mpu_complex * ) x, ( mpu_real * ) r, ( mpu_real * ) j, nb / 2 ); + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".cptr" ) || streqi( mn, ".codeptr" ) ) + { + __mpu_uint64_t zero = 0; + if( ac != 1 || !*av[0] ) + { + snprintf( err, es, ".cptr requires one CODE expression" ); + return( -1 ); + } + if( addfix( cptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".ptr" ) ) + { + __mpu_uint64_t zero = 0; + if( ac != 1 || !*av[0] ) + { + snprintf( err, es, ".ptr requires one pointer expression" ); + return( -1 ); + } + if( addfix( ptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + snprintf( err, es, "unknown data directive: %s", mn ); + return( -1 ); +} + +static int +valid_symbol_name( const char *s ) +{ + if( !s || !symfirst( (unsigned char)*s ) ) return( 0 ); + for( s++; *s; s++ ) if( !symrest( (unsigned char)*s ) ) return( 0 ); + return( 1 ); +} + +static int +finddecl( const symdecls_t *d, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < d->n; i++ ) if( !strcmp( d->p[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int adddecl( symdecls_t *d, const char *name, int is_global, int is_extern, int is_weak, + char *err, size_t es) +{ + size_t i; + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "bad symbol name: %s", name ); + return( -1 ); + } + if( !strcmp( name, "_start_life" ) ) + { + snprintf( err, es, "_start_life is an assembler pseudo-label and cannot be declared" ); + return( -1 ); + } + if( is_weak && ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) ) + { + snprintf( err, es, "lifecycle symbol %s cannot be weak", name ); + return( -1 ); + } + if( !finddecl( d, name, &i ) ) + { + if( is_global ) d->p[i].is_global = 1; + if( is_extern ) d->p[i].is_extern = 1; + if( is_weak ) d->p[i].is_weak = 1; + return( 0 ); + } + if( grow( (void **)&d->p, &d->cap, d->n + 1, sizeof( *d->p ) ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memset( &d->p[d->n], 0, sizeof( *d->p ) ); + strcpy( d->p[d->n].name, name ); + d->p[d->n].is_global = is_global; + d->p[d->n].is_extern = is_extern; + d->p[d->n].is_weak = is_weak; + d->n++; + return( 0 ); +} + +/* Return 1 for a declaration line, 0 otherwise, -1 on error. */ +static int +collect_decl_line( char *line, symdecls_t *decls, int line_no, char *err, size_t es ) +{ + char *p = trim( line ), *mn = p, *rest, *av[256]; + int ac, i, is_global = 0, is_extern = 0, is_weak = 0; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, ".global" ) || streqi( mn, ".globl" ) ) is_global = 1; + else + if( streqi( mn, ".extern" ) ) + { + is_global = 1; + is_extern = 1; + } + else + if( streqi( mn, ".weak" ) ) + { + is_weak = 1; + } + else return( 0 ); + if( !*rest ) + { + snprintf( err, es, "line %d: %s requires one or more symbol names", line_no, mn ); + return( -1 ); + } + ac = splitargs( rest, av, 256 ); + if( ac < 1 ) + { + snprintf( err, es, "line %d: malformed %s", line_no, mn ); + return( -1 ); + } + for( i = 0; i < ac; i++ ) if( adddecl( decls, av[i], is_global, is_extern, is_weak, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + return( -1 ); + } + return( 1 ); +} + +/* .bss is SHT_NOBITS: only layout directives are legal and no payload + bytes are emitted into the relocatable object. */ +static int bssdir( size_t *bss_size, char *line, __mpu_uint64_t *section_align, + equs_t *eqs, char *err, size_t es) +{ + char *p = trim( line ), *mn = p, *rest; + __mpu_uint64_t v; + + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + + if( streqi( mn, ".align" ) ) + { + __mpu_uint64_t rem, add; + int er; + if( !*rest ) + { + snprintf( err, es, ".align requires a value" ); + return( -1 ); + } + er = eval_u64( rest, eqs, 0, &v, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || !v || v > (__mpu_uint64_t)SIZE_MAX || ( v & ( v - 1 ) ) ) + { + snprintf( err, es, "bad .align" ); + return( -1 ); + } + if( section_align && v > *section_align ) * section_align = v; + rem = (__mpu_uint64_t)( *bss_size ) % v; + add = rem ? v - rem : 0; + if( add > (__mpu_uint64_t)SIZE_MAX - *bss_size ) + { + snprintf( err, es, ".bss size overflow" ); + return( -1 ); + } + *bss_size+=(size_t)add; + return( 0 ); + } + + if( streqi( mn, ".zero" ) ) + { + int er; + if( !*rest ) + { + snprintf( err, es, ".zero requires a byte count" ); + return( -1 ); + } + er = eval_u64( rest, eqs, 0, &v, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || v > (__mpu_uint64_t)SIZE_MAX - *bss_size ) + { + snprintf( err, es, "bad .zero byte count" ); + return( -1 ); + } + *bss_size+=(size_t)v; + return( 0 ); + } + + snprintf( err, es, "only .align and .zero are valid in .bss" ); + return( -1 ); +} + +/* Return 1 when line is .equ, 0 when it is not, -1 on malformed .equ. */ +static int +collect_equ_line( char *line, equs_t *eqs, int line_no, char *err, size_t es ) +{ + char *p = trim( line ), *mn = p, *rest, *av[2]; + int ac; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( !streqi( mn, ".equ" ) ) return( 0 ); + if( !*rest ) + { + snprintf( err, es, "line %d: .equ requires NAME, expression", line_no ); + return( -1 ); + } + ac = splitargs( rest, av, 2 ); + if( ac != 2 || !valid_symbol_name( av[0] ) || !*av[1] ) + { + snprintf( err, es, "line %d: .equ requires NAME, expression", line_no ); + return( -1 ); + } + if( addequ( eqs, av[0], av[1], line_no, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + return( -1 ); + } + return( 1 ); +} + +enum asm_section +{ + ASM_SEC_NONE = 0, + ASM_SEC_TEXT, + ASM_SEC_DATA, + ASM_SEC_BSS, + ASM_SEC_INIT, + ASM_SEC_FINI, + ASM_SEC_CONTROL, + ASM_SEC_INPUT, + ASM_SEC_OUTPUT, + ASM_SEC_TRACE +}; + +static int label_in_any( const char *name, const labels_t *a, const labels_t *b, + const labels_t *c, const labels_t *d, const labels_t *e, + const labels_t *f) +{ + __mpu_uint64_t x; + return( a && !findlabel( a, name, &x ) ) || ( b && !findlabel( b, name, &x ) ) || + ( c && !findlabel( c, name, &x ) ) || ( d && !findlabel( d, name, &x ) ) || + ( e && !findlabel( e, name, &x ) ) || ( f && !findlabel( f, name, &x ) ); +} + +static int resolve_ptr_expression( const char *expr, equs_t *eqs, + const labels_t *code, const labels_t *data, + const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + const size_t sizes[8], __mpu_uint64_t *ptr, + char *err, size_t es) +{ + const labels_t *ls[6] = + { + data, control, input, output, trace, code + }; + const unsigned segs[6] = + { + CPU_PTR_SEG_DATA, CPU_PTR_SEG_CONTROL, CPU_PTR_SEG_INPUT, + CPU_PTR_SEG_OUTPUT, CPU_PTR_SEG_TRACE, CPU_PTR_SEG_PROGRAM + }; + __mpu_uint64_t value = 0; + int i, found = 0, er; + unsigned found_seg = CPU_PTR_SEG_DATA; + char tmp[256]; + + /* A pure absolute/.equ expression keeps historical LEA semantics: DATA. */ + er = eval_u64( expr, eqs, 0, &value, tmp, sizeof( tmp ) ); + if( er == 0 ) + { + if( value > sizes[CPU_PTR_SEG_DATA] ) + { + snprintf( err, es, "LEA DATA expression is outside DATA: %s", expr ); + return( -1 ); + } + return( __cpu_ptr_make( ptr, CPU_PTR_SEG_DATA, value ) ); + } + + for( i = 0; i < 6; i++ ) + { + __mpu_uint64_t v; + if( eval_u64( expr, eqs, ls[i], &v, tmp, sizeof( tmp ) ) == 0 ) + { + found++; + value = v; + found_seg = segs[i]; + } + } + if( found != 1 ) + { + snprintf( err, es, found ? "ambiguous pointer expression: %s" : "unknown symbol in pointer expression: %s", expr ); + return( -1 ); + } + if( value > sizes[found_seg] ) + { + snprintf( err, es, "pointer expression is outside its segment: %s", expr ); + return( -1 ); + } + if( found_seg == CPU_PTR_SEG_HEAP && value < CPU_HEAP_BEGIN ) + { + snprintf( err, es, "pointer expression enters reserved HEAP guard: %s", expr ); + return( -1 ); + } + if( __cpu_ptr_make( ptr, found_seg, value ) ) + { + snprintf( err, es, "cannot encode PTR64 expression: %s", expr ); + return( -1 ); + } + return( 0 ); +} + +static int apply_cptr_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs, + const labels_t *clabels, size_t code_size, + char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + __mpu_uint64_t a, ptr; + int er = eval_u64( fx->p[i].expr, eqs, clabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, ".cptr expression '%s': %s", fx->p[i].expr, cause ); + return( -1 ); + } + if( er > 0 || a >= code_size ) + { + snprintf( err, es, ".cptr expression does not address PROGRAM: %s", fx->p[i].expr ); + return( -1 ); + } + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, a ) ) + { + snprintf( err, es, "cannot encode PROGRAM PTR64 for: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr ); + } + return( 0 ); +} + +static int apply_ptr_data_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs, + const labels_t *code, const labels_t *data, + const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + const size_t sizes[8], char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + __mpu_uint64_t ptr; + if( resolve_ptr_expression( fx->p[i].expr, eqs, code, data, control, input, output, trace, + sizes, &ptr, err, es)) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, ".ptr expression '%s': %s", fx->p[i].expr, cause ); + return( -1 ); + } + __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr ); + } + return( 0 ); +} + +int +cpu_assemble_text( const char *src, cpu_asm_image *im, char *err, size_t es ) +{ + char *copy = 0, *scan = 0, *save, *line; + buf_t code = + { + 0 + } + , data = + { + 0 + } + , control = + { + 0 + } + , input = + { + 0 + } + , output = + { + 0 + } + , trace = + { + 0 + }; + labels_t clabels = + { + 0 + } + , dlabels = + { + 0 + } + , ctlabels = + { + 0 + } + , ilabels = + { + 0 + } + , olabels = + { + 0 + } + , tlabels = + { + 0 + }; + fixups_t dfix = + { + 0 + } + , cfix = + { + 0 + } + , pfix = + { + 0 + }; + fixups_t dcptr = + { + 0 + } + , ccptr = + { + 0 + } + , icptr = + { + 0 + } + , ocptr = + { + 0 + } + , tcptr = + { + 0 + }; + fixups_t dptr = + { + 0 + } + , ctptr = + { + 0 + } + , iptr = + { + 0 + } + , optr = + { + 0 + } + , tptr = + { + 0 + }; + __mpu_uint64_t dalign = 1, ctalign = 1, ialign = 1, oalign = 1, talign = 1; + equs_t eqs = + { + 0 + }; + int section = ASM_SEC_NONE, line_no = 0, rc = -1; + int init_seen = 0, text_seen = 0, fini_seen = 0, start_seen = 0, life_seen = 0, fini_entry_seen = 0; + __mpu_uint64_t first_text = 0, start_entry = 0, life_entry = 0, fini_entry = 0; + + if( !src || !im ) return( -1 ); + memset( im, 0, sizeof( *im ) ); + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + snprintf( err, es, "assembler source is not valid UTF-8" ); + return( -1 ); + } + + /* Resolve absolute .equ symbols first. Section labels intentionally do not + participate in .equ, preserving the distinction between absolute symbols + and relocatable/segment-relative labels. */ + scan = strdup( src ); + if( !scan ) return( -1 ); + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_equ_line( line, &eqs, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + { + size_t i; + __int128 v; + for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + /* DATA keeps its ABI NULL guard. Other image-backed data spaces begin at + offset zero because their non-zero segment selector already distinguishes + them from the all-zero NULL PTR64. */ + while( data.n < CPU_DATA_NULL_GUARD_SIZE ) + if( put8( &data, 0 ) ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + + copy = strdup( src ); + if( !copy ) goto out; + line_no = 0; + for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + char *p, *colon, *mn, *rest, *q; + buf_t *db = 0; + labels_t *dl = 0; + fixups_t *cpfx = 0, *pfx = 0; + __mpu_uint64_t *salign = 0; + line_no++; + strip_comment( line ); + p = trim( line ); + if( !*p ) continue; + + q = p; + while( *q && !isspace( (unsigned char)*q ) ) q++; + if( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) continue; + + if( streqi( p, ".text" ) ) + { + if( !text_seen ) + { + first_text = (__mpu_uint64_t)code.n; + text_seen = 1; + } + section = ASM_SEC_TEXT; + continue; + } + if( streqi( p, ".init" ) ) + { + init_seen = 1; + section = ASM_SEC_INIT; + continue; + } + if( streqi( p, ".fini" ) ) + { + fini_seen = 1; + section = ASM_SEC_FINI; + continue; + } + if( streqi( p, ".data" ) ) + { + section = ASM_SEC_DATA; + continue; + } + if( streqi( p, ".control" ) ) + { + section = ASM_SEC_CONTROL; + continue; + } + if( streqi( p, ".input" ) ) + { + section = ASM_SEC_INPUT; + continue; + } + if( streqi( p, ".output" ) ) + { + section = ASM_SEC_OUTPUT; + continue; + } + if( streqi( p, ".trace" ) ) + { + section = ASM_SEC_TRACE; + continue; + } + + colon = label_colon( p ); + if( colon ) + { + size_t ei; + char *name; + *colon = 0; + name = trim( p ); + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "line %d: bad label name", line_no ); + goto out; + } + if( !findequ( &eqs, name, &ei ) ) + { + snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name ); + goto out; + } + if( label_in_any( name, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels ) ) + { + snprintf( err, es, "line %d: duplicate label: %s", line_no, name ); + goto out; + } + if( section == ASM_SEC_TEXT || section == ASM_SEC_INIT || section == ASM_SEC_FINI ) + { + if( addlabel( &clabels, name, code.n ) ) + { + snprintf( err, es, "line %d: bad/duplicate CODE label", line_no ); + goto out; + } + if( !strcmp( name, "_start" ) ) + { + if( section != ASM_SEC_INIT ) + { + snprintf( err, es, "line %d: _start must be in .init", line_no ); + goto out; + } + start_seen = 1; + start_entry = code.n; + } + if( !strcmp( name, "_life" ) ) + { + if( section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _life must be in .text", line_no ); + goto out; + } + life_seen = 1; + life_entry = code.n; + } + if( !strcmp( name, "_fini" ) ) + { + if( section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: _fini must be in .fini", line_no ); + goto out; + } + fini_entry_seen = 1; + fini_entry = code.n; + } + } + else + { + switch( section ) + { + case ASM_SEC_DATA: dl = &dlabels; + break; + case ASM_SEC_CONTROL: dl = &ctlabels; + break; + case ASM_SEC_INPUT: dl = &ilabels; + break; + case ASM_SEC_OUTPUT: dl = &olabels; + break; + case ASM_SEC_TRACE: dl = &tlabels; + break; + default: break; + } + if( !dl || addlabel( dl, name, ( section == ASM_SEC_DATA ) ? data.n : ( section == ASM_SEC_CONTROL ) ? control.n : ( section == ASM_SEC_INPUT ) ? input.n : ( section == ASM_SEC_OUTPUT ) ? output.n : trace.n ) ) + { + snprintf( err, es, "line %d: label outside a valid section or duplicate label", line_no ); + goto out; + } + } + p = trim( colon + 1 ); + if( !*p ) continue; + } + + switch( section ) + { + case ASM_SEC_DATA: db = &data; + cpfx = &dcptr; + pfx = &dptr; + salign = &dalign; + break; + case ASM_SEC_CONTROL: db = &control; + cpfx = &ccptr; + pfx = &ctptr; + salign = &ctalign; + break; + case ASM_SEC_INPUT: db = &input; + cpfx = &icptr; + pfx = &iptr; + salign = &ialign; + break; + case ASM_SEC_OUTPUT: db = &output; + cpfx = &ocptr; + pfx = &optr; + salign = &oalign; + break; + case ASM_SEC_TRACE: db = &trace; + cpfx = &tcptr; + pfx = &tptr; + salign = &talign; + break; + default:break; + } + if( db ) + { + if( datadir( db, p, cpfx, pfx, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( section != ASM_SEC_TEXT && section != ASM_SEC_INIT && section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: select a code or data section", line_no ); + goto out; + } + + mn = p; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, "LDI" ) ) + { + if( !*rest || emit_ldi( &code, &eqs, rest, err, es ) ) goto badline; + } + else + { + if( emit_instruction( &code, &dfix, &cfix, &pfix, &eqs, mn, rest, err, es ) ) goto badline; + } continue; +badline: + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + if( init_seen && !start_seen ) + { + snprintf( err, es, ".init section requires ABI entry label _start" ); + goto out; + } + if( fini_seen && !fini_entry_seen ) + { + snprintf( err, es, ".fini section requires ABI entry label _fini" ); + goto out; + } + if( !text_seen ) + { + snprintf( err, es, "program requires a .text section" ); + goto out; + } + if( !life_seen ) life_entry = first_text; + /* backward-compatible legacy .text image */ + + { + size_t i; + size_t sizes[8] = + { + data.n, 0, code.n, control.n, input.n, output.n, trace.n, 0 + }; + for( i = 0; i < dfix.n; i++ ) + { + __mpu_uint64_t a; + int er = eval_u64( dfix.p[i].expr, &eqs, &dlabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, "DATA expression '%s': %s", dfix.p[i].expr, cause ); + goto out; + } + if( er > 0 ) + { + snprintf( err, es, "DATA expression out of unsigned 64-bit range: %s", dfix.p[i].expr ); + goto out; + } + __cpu_code_put_u64( code.p + dfix.p[i].pos, a ); + } + for( i = 0; i < cfix.n; i++ ) + { + __mpu_uint64_t a; + int er = eval_u64( cfix.p[i].expr, &eqs, &clabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, "CODE expression '%s': %s", cfix.p[i].expr, cause ); + goto out; + } + if( er > 0 || a >= code.n ) + { + snprintf( err, es, "CODE expression does not address PROGRAM: %s", cfix.p[i].expr ); + goto out; + } + __cpu_code_put_u64( code.p + cfix.p[i].pos, a ); + } + for( i = 0; i < pfix.n; i++ ) + { + __mpu_uint64_t ptr; + if( resolve_ptr_expression( pfix.p[i].expr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, &ptr, err, es ) ) goto out; + __cpu_code_put_u64( code.p + pfix.p[i].pos, ptr ); + } + if( apply_cptr_fixups( &data, &dcptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &control, &ccptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &input, &icptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &output, &ocptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &trace, &tcptr, &eqs, &clabels, code.n, err, es ) || + apply_ptr_data_fixups( &data, &dptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &control, &ctptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &input, &iptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &output, &optr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &trace, &tptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es )) goto out; + } + + im->code = code.p; + im->code_size = code.n; + code.p = 0; + im->data = data.p; + im->data_size = data.n; + data.p = 0; + im->control = control.p; + im->control_size = control.n; + control.p = 0; + im->input = input.p; + im->input_size = input.n; + input.p = 0; + im->output = output.p; + im->output_size = output.n; + output.p = 0; + im->trace = trace.p; + im->trace_size = trace.n; + trace.p = 0; + im->has_init = init_seen; + im->init_entry = start_entry; + im->has_life = 1; + im->life_entry = life_entry; + im->has_fini = fini_seen; + im->fini_entry = fini_entry; + rc = 0; + +out: + free( scan ); + free( copy ); + free( code.p ); + free( data.p ); + free( control.p ); + free( input.p ); + free( output.p ); + free( trace.p ); + free( clabels.p ); + free( dlabels.p ); + free( ctlabels.p ); + free( ilabels.p ); + free( olabels.p ); + free( tlabels.p ); + free( dfix.p ); + free( cfix.p ); + free( pfix.p ); + free( dcptr.p ); + free( ccptr.p ); + free( icptr.p ); + free( ocptr.p ); + free( tcptr.p ); + free( dptr.p ); + free( ctptr.p ); + free( iptr.p ); + free( optr.p ); + free( tptr.p ); + free( eqs.p ); + return( rc ); +} + +void +cpu_asm_image_free( cpu_asm_image *im ) +{ + size_t i; + if( !im ) return; + free( im->code ); + free( im->data ); + free( im->control ); + free( im->input ); + free( im->output ); + free( im->trace ); + for( i = 0; i < im->dyn_symbol_count; i++ ) free( im->dyn_symbols[i].name ); + free( im->dyn_symbols ); + free( im->dyn_relocs ); + memset( im, 0, sizeof( *im ) ); +} + +/* ------------------------------------------------------------------------- + * Relocatable assembler path -- MELF logical sections. + * + * ET_REL keeps .init/.text/.fini distinct and stores explicit RELA-like + * semantic records in cpu_asm_object. cpu-object.c maps this logical object + * one-to-one onto MELF sections/symbols/RELA records. + * ------------------------------------------------------------------------- */ + +static int +obj_symbol_defined( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) + if( !strcmp( o->symbols[i].name, name ) && o->symbols[i].section != CPU_OBJ_SEC_UNDEF ) + { + if( idx ) * idx = i; + return( 1 ); + } + return( 0 ); +} + +static int +obj_find_symbol( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) if( !strcmp( o->symbols[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int obj_add_symbol( cpu_asm_object *o, const char *name, unsigned section, unsigned binding, unsigned type, + __mpu_uint64_t value, char *err, size_t es) +{ + cpu_asm_symbol *q; + size_t i; + if( !obj_find_symbol( o, name, &i ) ) + { + cpu_asm_symbol *x = &o->symbols[i]; + if( x->section != CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF ) + { + snprintf( err, es, "duplicate object symbol: %s", name ); + return( -1 ); + } + if( x->section == CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF ) + { + x->section = section; + x->value = value; + x->type = type; + } + if( binding == CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_WEAK; + else if( binding == CPU_OBJ_BIND_GLOBAL && x->binding != CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_GLOBAL; + return( 0 ); + } + q = (cpu_asm_symbol *)realloc( o->symbols, ( o->symbol_count + 1 ) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + o->symbols = q; + memset( &o->symbols[o->symbol_count], 0, sizeof( o->symbols[o->symbol_count] ) ); + o->symbols[o->symbol_count].name = strdup( name ); + if( !o->symbols[o->symbol_count].name ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + o->symbols[o->symbol_count].section = section; + o->symbols[o->symbol_count].binding = binding; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + o->symbol_count++; + return( 0 ); +} + +static int obj_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, size_t sym, + __mpu_uint64_t off, __mpu_int64_t addend, char *err, size_t es) +{ + cpu_asm_reloc *q = (cpu_asm_reloc *)realloc( o->relocs, ( o->reloc_count + 1 ) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + o->relocs = q; + o->relocs[o->reloc_count].section = section; + o->relocs[o->reloc_count].type = type; + o->relocs[o->reloc_count].symbol_index = sym; + o->relocs[o->reloc_count].offset = off; + o->relocs[o->reloc_count].addend = addend; + o->reloc_count++; + return( 0 ); +} + +static int obj_label_lookup( const char *name, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + unsigned *section, __mpu_uint64_t *value) +{ + if( init && !findlabel( init, name, value ) ) + { + *section = CPU_OBJ_SEC_INIT; + return( 0 ); + } + if( text && !findlabel( text, name, value ) ) + { + *section = CPU_OBJ_SEC_TEXT; + return( 0 ); + } + if( fini && !findlabel( fini, name, value ) ) + { + *section = CPU_OBJ_SEC_FINI; + return( 0 ); + } + if( data && !findlabel( data, name, value ) ) + { + *section = CPU_OBJ_SEC_DATA; + return( 0 ); + } + if( bss && !findlabel( bss, name, value ) ) + { + *section = CPU_OBJ_SEC_BSS; + return( 0 ); + } + if( control && !findlabel( control, name, value ) ) + { + *section = CPU_OBJ_SEC_CONTROL; + return( 0 ); + } + if( input && !findlabel( input, name, value ) ) + { + *section = CPU_OBJ_SEC_INPUT; + return( 0 ); + } + if( output && !findlabel( output, name, value ) ) + { + *section = CPU_OBJ_SEC_OUTPUT; + return( 0 ); + } + if( trace && !findlabel( trace, name, value ) ) + { + *section = CPU_OBJ_SEC_TRACE; + return( 0 ); + } + return( -1 ); +} + +static int obj_name_defined_any( const char *name, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace) +{ + unsigned s; + __mpu_uint64_t v; + return( !obj_label_lookup( name, init, text, fini, data, bss, control, input, output, trace, &s, &v ) ); +} + +static int reloc_expression_melf( const char *expr, equs_t *eqs, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + char sym[ASM_NAME_MAX], int *has_sym, __mpu_int64_t *addend, + __mpu_uint64_t *absolute, char *err, size_t es) +{ + const char *p = expr; + char found[ASM_NAME_MAX] = ""; + int seen = 0; + while( *p ) + { + if( isdigit( (unsigned char)*p ) ) + { + char *end = 0; + errno = 0; + (void)strtoull( p, &end, 0 ); + if( end && end > p ) + { + p = end; + continue; + } + } + if( symfirst( (unsigned char)*p ) ) + { + char n[ASM_NAME_MAX]; + size_t k = 0, ei, di; + unsigned sec; + __mpu_uint64_t val; + while( symrest( (unsigned char)*p ) ) + { + if( k + 1 >= sizeof( n ) ) + { + snprintf( err, es, "symbol name too long" ); + return( -1 ); + } + n[k++] = *p++; + } + n[k] = 0; + if( !findequ( eqs, n, &ei ) ) continue; + if( !obj_label_lookup( n, init, text, fini, data, bss, control, input, output, trace, &sec, &val ) || !finddecl( decls, n, &di ) ) + { + if( !seen ) + { + strcpy( found, n ); + seen = 1; + } + else + if( strcmp( found, n ) ) + { + snprintf( err, es, "relocatable expression contains more than one symbol: %s", expr ); + return( -1 ); + } continue; + } + snprintf( err, es, "unknown symbol in relocatable expression: %s", n ); + return( -1 ); + } + p++; + } + if( !seen ) + { + __int128 v; + if( eval_expression( expr, eqs, 0, &v, err, es ) ) return( -1 ); + if( v < 0 || v > ( __int128 ) UINT64_MAX ) + { + snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr ); + return( -1 ); + } + *has_sym = 0; + *absolute = (__mpu_uint64_t)v; + return( 0 ); + } + else + { + labels_t fake = + { + 0 + }; + label_t one; + __int128 v0, v1, d; + memset( &one, 0, sizeof( one ) ); + strcpy( one.name, found ); + fake.p = &one; + fake.n = fake.cap = 1; + one.addr = 0; + if( eval_expression( expr, eqs, &fake, &v0, err, es ) ) return( -1 ); + one.addr = 1; + if( eval_expression( expr, eqs, &fake, &v1, err, es ) ) return( -1 ); + d = v1 - v0; + if( d == 0 ) + { + if( v0 < 0 || v0 > ( __int128 ) UINT64_MAX ) + { + snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr ); + return( -1 ); + } + *has_sym = 0; + *absolute = (__mpu_uint64_t)v0; + return( 0 ); + } + if( d != 1 ) + { + snprintf( err, es, "relocatable expression must be SYMBOL + constant: %s", expr ); + return( -1 ); + } + if( v0 < ( __int128 ) INT64_MIN || v0 > ( __int128 ) INT64_MAX ) + { + snprintf( err, es, "relocation addend out of signed 64-bit range: %s", expr ); + return( -1 ); + } + strcpy( sym, found ); + *has_sym = 1; + *addend = (__mpu_int64_t)v0; + return( 0 ); + } +} + +static int process_obj_fixups_melf( cpu_asm_object *o, buf_t *src, unsigned srcsec, const fixups_t *fx, + unsigned type, equs_t *eqs, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + char name[ASM_NAME_MAX]; + int hs = 0; + __mpu_int64_t add = 0; + __mpu_uint64_t abs = 0, ptr; + size_t si; + if( reloc_expression_melf( fx->p[i].expr, eqs, decls, init, text, fini, data, bss, control, input, output, trace, name, &hs, &add, &abs, err, es ) ) return( -1 ); + if( hs ) + { + if( obj_find_symbol( o, name, &si ) ) + { + snprintf( err, es, "internal assembler symbol lookup failed: %s", name ); + return( -1 ); + } + if( o->symbols[si].section != CPU_OBJ_SEC_UNDEF ) + { + unsigned ts = o->symbols[si].section; + if( type == CPU_RELOC_CODE64 && !cpu_obj_sec_is_program( ts ) ) + { + snprintf( err, es, "CODE64 relocation target is not PROGRAM: %s", name ); + return( -1 ); + } + if( type == CPU_RELOC_DATA64 && ts != CPU_OBJ_SEC_DATA && ts != CPU_OBJ_SEC_BSS ) + { + snprintf( err, es, "ADDR64 relocation target is not DATA: %s", name ); + return( -1 ); + } + if( type == CPU_RELOC_CPTR64_DATA && !cpu_obj_sec_is_program( ts ) ) + { + snprintf( err, es, ".cptr relocation target is not PROGRAM: %s", name ); + return( -1 ); + } + } + if( obj_add_reloc( o, srcsec, type, si, fx->p[i].pos, add, err, es ) ) return( -1 ); + continue; + } + switch( type ) + { + case CPU_RELOC_CODE64: case CPU_RELOC_DATA64: __cpu_code_put_u64( src->p + fx->p[i].pos, abs ); + break; + case CPU_RELOC_PTR64_CODE: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) ) + { + snprintf( err, es, "absolute LEA pointer cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_code_put_u64( src->p + fx->p[i].pos, ptr ); + break; + case CPU_RELOC_PTR64_DATA: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) ) + { + snprintf( err, es, "absolute .ptr cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( src->p + fx->p[i].pos, ptr ); + break; + case CPU_RELOC_CPTR64_DATA: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, abs ) ) + { + snprintf( err, es, "absolute .cptr cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( src->p + fx->p[i].pos, ptr ); + break; + default: snprintf( err, es, "internal unknown relocation type" ); + return( -1 ); + } + } + return( 0 ); +} + +static unsigned +declared_binding( const symdecls_t *d, const char *name ) +{ + size_t i; + if( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) return( CPU_OBJ_BIND_GLOBAL ); + if( !finddecl( d, name, &i ) ) + { + if( d->p[i].is_weak ) return( CPU_OBJ_BIND_WEAK ); + if( d->p[i].is_global ) return( CPU_OBJ_BIND_GLOBAL ); + } + return( CPU_OBJ_BIND_LOCAL ); +} + +static int build_obj_symbols_melf( cpu_asm_object *o, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, char *err, size_t es) +{ + const labels_t *ls[9] = + { + init, text, fini, data, bss, control, input, output, trace + }; + const unsigned sec[9] = + { + CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE + }; + size_t j, i; + for( j = 0; j < 9; j++ ) for( i = 0; i < ls[j]->n; i++ ) + { + unsigned type = cpu_obj_sec_is_program( sec[j] ) ? CPU_OBJ_TYPE_FUNC : CPU_OBJ_TYPE_OBJECT; + if( obj_add_symbol( o, ls[j]->p[i].name, sec[j], declared_binding( decls, ls[j]->p[i].name ), type, ls[j]->p[i].addr, err, es ) ) return( -1 ); + } + for( i = 0; i < decls->n; i++ ) if( !obj_symbol_defined( o, decls->p[i].name, 0 ) ) + { + const char *name = decls->p[i].name; + unsigned b = decls->p[i].is_weak ? CPU_OBJ_BIND_WEAK : CPU_OBJ_BIND_GLOBAL; + unsigned type = ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) + ?CPU_OBJ_TYPE_FUNC:CPU_OBJ_TYPE_NOTYPE; + if( obj_add_symbol( o, name, CPU_OBJ_SEC_UNDEF, b, type, 0, err, es ) ) return( -1 ); + } + return( 0 ); +} + +int +cpu_assemble_object( const char *src, cpu_asm_object *o, char *err, size_t es ) +{ + char *copy = 0, *scan = 0, *save = 0, *line; + buf_t init = + { + 0 + } + , text = + { + 0 + } + , fini = + { + 0 + } + , data = + { + 0 + } + , control = + { + 0 + } + , input = + { + 0 + } + , output = + { + 0 + } + , trace = + { + 0 + }; + size_t bss_size = 0; + labels_t ilabs = + { + 0 + } + , tlabs = + { + 0 + } + , flabs = + { + 0 + } + , dlabs = + { + 0 + } + , blabs = + { + 0 + } + , ctlabs = + { + 0 + } + , inlabs = + { + 0 + } + , olabs = + { + 0 + } + , trlabs = + { + 0 + }; + fixups_t idfix = + { + 0 + } + , icfix = + { + 0 + } + , ipfix = + { + 0 + } + , tdfix = + { + 0 + } + , tcfix = + { + 0 + } + , tpfix = + { + 0 + } + , fdfix = + { + 0 + } + , fcfix = + { + 0 + } + , fpfix = + { + 0 + }; + fixups_t dcptr = + { + 0 + } + , ccptr = + { + 0 + } + , incptr = + { + 0 + } + , ocptr = + { + 0 + } + , trcptr = + { + 0 + }; + fixups_t dptr = + { + 0 + } + , ctptr = + { + 0 + } + , inptr = + { + 0 + } + , optr = + { + 0 + } + , trptr = + { + 0 + }; + __mpu_uint64_t dalign = 1, balign = 1, ctalign = 1, inalign = 1, oalign = 1, tralign = 1; + equs_t eqs = + { + 0 + }; + symdecls_t decls = + { + 0 + }; + int section = ASM_SEC_NONE, line_no = 0, rc = -1; + int init_seen = 0, text_seen = 0, fini_seen = 0, bss_seen = 0, pseudo_seen = 0; + + if( !src || !o ) return( -1 ); + memset( o, 0, sizeof( *o ) ); + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + snprintf( err, es, "assembler source is not valid UTF-8" ); + return( -1 ); + } + + scan = strdup( src ); + if( !scan ) return( -1 ); + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_equ_line( line, &eqs, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + save = 0; + { + size_t i; + __int128 v; + for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + scan = strdup( src ); + if( !scan ) goto out; + line_no = 0; + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_decl_line( line, &decls, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + save = 0; + { + size_t i, j; + for( i = 0; i < eqs.n; i++ ) for( j = 0; j < decls.n; j++ ) if( !strcmp( eqs.p[i].name, decls.p[j].name ) ) + { + snprintf( err, es, "symbol declared global/extern/weak conflicts with .equ: %s", eqs.p[i].name ); + goto out; + } + } + + copy = strdup( src ); + if( !copy ) goto out; + line_no = 0; + for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + char *p, *colon, *mn, *rest, *q; + buf_t *cb = 0, *db = 0; + labels_t *labs = 0; + fixups_t *df = 0, *cf = 0, *pf = 0, *cpfx = 0, *gptr = 0; + __mpu_uint64_t *salign = 0; + int in_bss = 0; + line_no++; + strip_comment( line ); + p = trim( line ); + if( !*p ) continue; + q = p; + while( *q && !isspace( (unsigned char)*q ) ) q++; + if( ( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".global", 7 ) ) || ( (size_t)( q - p ) == 6 && !strncasecmp( p, ".globl", 6 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".extern", 7 ) ) || ( (size_t)( q - p ) == 5 && !strncasecmp( p, ".weak", 5 ) ) ) continue; + + if( streqi( p, ".init" ) ) + { + init_seen = 1; + section = ASM_SEC_INIT; + continue; + } + if( streqi( p, ".text" ) ) + { + text_seen = 1; + section = ASM_SEC_TEXT; + continue; + } + if( streqi( p, ".fini" ) ) + { + fini_seen = 1; + section = ASM_SEC_FINI; + continue; + } + if( streqi( p, ".data" ) ) + { + section = ASM_SEC_DATA; + continue; + } + if( streqi( p, ".bss" ) ) + { + bss_seen = 1; + section = ASM_SEC_BSS; + continue; + } + if( streqi( p, ".control" ) ) + { + section = ASM_SEC_CONTROL; + continue; + } + if( streqi( p, ".input" ) ) + { + section = ASM_SEC_INPUT; + continue; + } + if( streqi( p, ".output" ) ) + { + section = ASM_SEC_OUTPUT; + continue; + } + if( streqi( p, ".trace" ) ) + { + section = ASM_SEC_TRACE; + continue; + } + + switch( section ) + { + case ASM_SEC_INIT: cb = &init; + labs = &ilabs; + df = &idfix; + cf = &icfix; + pf = &ipfix; + break; + case ASM_SEC_TEXT: cb = &text; + labs = &tlabs; + df = &tdfix; + cf = &tcfix; + pf = &tpfix; + break; + case ASM_SEC_FINI: cb = &fini; + labs = &flabs; + df = &fdfix; + cf = &fcfix; + pf = &fpfix; + break; + case ASM_SEC_DATA: db = &data; + labs = &dlabs; + cpfx = &dcptr; + gptr = &dptr; + salign = &dalign; + break; + case ASM_SEC_BSS: labs = &blabs; + salign = &balign; + in_bss = 1; + break; + case ASM_SEC_CONTROL: db = &control; + labs = &ctlabs; + cpfx = &ccptr; + gptr = &ctptr; + salign = &ctalign; + break; + case ASM_SEC_INPUT: db = &input; + labs = &inlabs; + cpfx = &incptr; + gptr = &inptr; + salign = &inalign; + break; + case ASM_SEC_OUTPUT: db = &output; + labs = &olabs; + cpfx = &ocptr; + gptr = &optr; + salign = &oalign; + break; + case ASM_SEC_TRACE: db = &trace; + labs = &trlabs; + cpfx = &trcptr; + gptr = &trptr; + salign = &tralign; + break; + default:break; + } + + colon = label_colon( p ); + if( colon ) + { + size_t ei; + char *name; + __mpu_uint64_t here; + *colon = 0; + name = trim( p ); + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "line %d: bad label name", line_no ); + goto out; + } + if( !findequ( &eqs, name, &ei ) ) + { + snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name ); + goto out; + } + if( !strcmp( name, "_start_life" ) ) + { + if( section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _start_life is valid only in .text", line_no ); + goto out; + } + if( pseudo_seen || obj_name_defined_any( "_start", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) || obj_name_defined_any( "_life", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) ) + { + snprintf( err, es, "line %d: _start_life conflicts with an existing _start/_life definition", line_no ); + goto out; + } + here = (__mpu_uint64_t)text.n; + if( addlabel( &tlabs, "_start", here ) || addlabel( &tlabs, "_life", here ) ) + { + snprintf( err, es, "line %d: cannot define _start_life", line_no ); + goto out; + } + pseudo_seen = 1; + } + else + { + if( obj_name_defined_any( name, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) ) + { + snprintf( err, es, "line %d: duplicate label: %s", line_no, name ); + goto out; + } + if( !labs ) + { + snprintf( err, es, "line %d: label outside a valid section", line_no ); + goto out; + } + here = cb ? (__mpu_uint64_t)cb->n : in_bss ? (__mpu_uint64_t)bss_size : (__mpu_uint64_t)db->n; + if( addlabel( labs, name, here ) ) + { + snprintf( err, es, "line %d: bad/duplicate label", line_no ); + goto out; + } + if( !strcmp( name, "_start" ) && section != ASM_SEC_INIT && section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _start must be in .init or .text", line_no ); + goto out; + } + if( !strcmp( name, "_life" ) && section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _life must be in .text", line_no ); + goto out; + } + if( !strcmp( name, "_fini" ) && section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: _fini must be in .fini", line_no ); + goto out; + } + } + p = trim( colon + 1 ); + if( !*p ) continue; + } + + if( in_bss ) + { + if( bssdir( &bss_size, p, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( db ) + { + if( datadir( db, p, cpfx, gptr, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( !cb ) + { + snprintf( err, es, "line %d: select a code or data section", line_no ); + goto out; + } + mn = p; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, "LDI" ) ) + { + if( !*rest || emit_ldi( cb, &eqs, rest, err, es ) ) goto badline_obj; + } + else if( emit_instruction( cb, df, cf, pf, &eqs, mn, rest, err, es ) ) goto badline_obj; + continue; +badline_obj: +{ + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; +} + } + + if( init_seen && obj_name_defined_any( "_start", 0, &tlabs, 0, 0, 0, 0, 0, 0, 0 ) ) + { + snprintf( err, es, "_start must be in .init when .init exists" ); + goto out; + } + if( init_seen && pseudo_seen ) + { + snprintf( err, es, "_start_life is invalid when .init exists" ); + goto out; + } + + if( build_obj_symbols_melf( o, &decls, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs, err, es ) ) goto out; +#define PF(SRC,SEC,FX,TYPE) process_obj_fixups_melf(o,&(SRC),(SEC),&(FX),(TYPE),&eqs,&decls,&ilabs,&tlabs,&flabs,&dlabs,&blabs,&ctlabs,&inlabs,&olabs,&trlabs,err,es) + if( PF( init, CPU_OBJ_SEC_INIT, idfix, CPU_RELOC_DATA64 ) || PF( init, CPU_OBJ_SEC_INIT, icfix, CPU_RELOC_CODE64 ) || PF( init, CPU_OBJ_SEC_INIT, ipfix, CPU_RELOC_PTR64_CODE ) || + PF( text, CPU_OBJ_SEC_TEXT, tdfix, CPU_RELOC_DATA64 ) || PF( text, CPU_OBJ_SEC_TEXT, tcfix, CPU_RELOC_CODE64 ) || PF( text, CPU_OBJ_SEC_TEXT, tpfix, CPU_RELOC_PTR64_CODE ) || + PF( fini, CPU_OBJ_SEC_FINI, fdfix, CPU_RELOC_DATA64 ) || PF( fini, CPU_OBJ_SEC_FINI, fcfix, CPU_RELOC_CODE64 ) || PF( fini, CPU_OBJ_SEC_FINI, fpfix, CPU_RELOC_PTR64_CODE ) || + PF( data, CPU_OBJ_SEC_DATA, dcptr, CPU_RELOC_CPTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ccptr, CPU_RELOC_CPTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, incptr, CPU_RELOC_CPTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, ocptr, CPU_RELOC_CPTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trcptr, CPU_RELOC_CPTR64_DATA ) || + PF( data, CPU_OBJ_SEC_DATA, dptr, CPU_RELOC_PTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ctptr, CPU_RELOC_PTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, inptr, CPU_RELOC_PTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, optr, CPU_RELOC_PTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trptr, CPU_RELOC_PTR64_DATA )) goto out; +#undef PF + + o->init = init.p; + o->init_size = init.n; + o->init_align = 1; + o->has_init = init_seen; + init.p = 0; + o->text = text.p; + o->text_size = text.n; + o->text_align = 1; + o->has_text = text_seen; + text.p = 0; + o->fini = fini.p; + o->fini_size = fini.n; + o->fini_align = 1; + o->has_fini = fini_seen; + fini.p = 0; + o->data = data.p; + o->data_size = data.n; + o->data_align = dalign; + o->has_data = ( data.n != 0 ); + data.p = 0; + o->bss_size = bss_size; + o->bss_align = balign; + o->has_bss = bss_seen; + o->control = control.p; + o->control_size = control.n; + o->control_align = ctalign; + o->has_control = ( control.n != 0 ); + control.p = 0; + o->input = input.p; + o->input_size = input.n; + o->input_align = inalign; + o->has_input = ( input.n != 0 ); + input.p = 0; + o->output = output.p; + o->output_size = output.n; + o->output_align = oalign; + o->has_output = ( output.n != 0 ); + output.p = 0; + o->trace = trace.p; + o->trace_size = trace.n; + o->trace_align = tralign; + o->has_trace = ( trace.n != 0 ); + trace.p = 0; + o->code_size = o->init_size + o->text_size + o->fini_size; + o->code_align = 1; + if( o->code_size ) + { + o->code = (__mpu_UNIT_t *)malloc( o->code_size ); + if( !o->code ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + memcpy( o->code, o->init, o->init_size ); + memcpy( o->code + o->init_size, o->text, o->text_size ); + memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size ); + } + rc = 0; +out: + free( scan ); + free( copy ); + free( init.p ); + free( text.p ); + free( fini.p ); + free( data.p ); + free( control.p ); + free( input.p ); + free( output.p ); + free( trace.p ); + free( ilabs.p ); + free( tlabs.p ); + free( flabs.p ); + free( dlabs.p ); + free( blabs.p ); + free( ctlabs.p ); + free( inlabs.p ); + free( olabs.p ); + free( trlabs.p ); + free( idfix.p ); + free( icfix.p ); + free( ipfix.p ); + free( tdfix.p ); + free( tcfix.p ); + free( tpfix.p ); + free( fdfix.p ); + free( fcfix.p ); + free( fpfix.p ); + free( dcptr.p ); + free( ccptr.p ); + free( incptr.p ); + free( ocptr.p ); + free( trcptr.p ); + free( dptr.p ); + free( ctptr.p ); + free( inptr.p ); + free( optr.p ); + free( trptr.p ); + free( eqs.p ); + free( decls.p ); + if( rc ) cpu_asm_object_free( o ); + return( rc ); +} + +void +cpu_asm_object_free( cpu_asm_object *o ) +{ + size_t i; + if( !o ) return; + free( o->init ); + free( o->text ); + free( o->fini ); + free( o->data ); + free( o->control ); + free( o->input ); + free( o->output ); + free( o->trace ); + free( o->code ); + for( i = 0; i < o->symbol_count; i++ ) free( o->symbols[i].name ); + free( o->symbols ); + free( o->relocs ); + memset( o, 0, sizeof( *o ) ); +} diff --git a/utils/mcpu-asm.h b/utils/mcpu-asm.h new file mode 100644 index 0000000..3ea7f42 --- /dev/null +++ b/utils/mcpu-asm.h @@ -0,0 +1,205 @@ +#ifndef __CPU_ASM_H +#define __CPU_ASM_H + +#include <stddef.h> +#include <libmcpu.h> + + +/* Runtime dynamic-link metadata carried by ABI-v2/ABI-v3 ET_EXEC/ET_DYN images. */ +typedef struct cpu_dyn_symbol +{ + char *name; + unsigned int section; /* logical output section, or CPU_OBJ_SEC_UNDEF */ + unsigned int binding; /* GLOBAL/WEAK */ + unsigned int type; /* NOTYPE/OBJECT/FUNC */ + __mpu_uint64_t value; /* output-section-relative byte offset */ + __mpu_uint64_t size; +} cpu_dyn_symbol; + +typedef struct cpu_dyn_reloc +{ + unsigned int section; /* logical output section containing field */ + unsigned int type; + size_t symbol_index; /* index in cpu_asm_image.dyn_symbols */ + __mpu_uint64_t offset; /* output-section-relative field offset */ + __mpu_int64_t addend; +} cpu_dyn_reloc; + +/* Final linked/runtime image used internally by mcpu-ld. */ +typedef struct cpu_asm_image +{ + __mpu_UNIT_t *code; + size_t code_size; + __mpu_UNIT_t *data; + size_t data_size; + size_t data_mem_size; + __mpu_UNIT_t *control; + size_t control_size; + __mpu_UNIT_t *input; + size_t input_size; + __mpu_UNIT_t *output; + size_t output_size; + __mpu_UNIT_t *trace; + size_t trace_size; + + int has_start; + int has_init; + int has_life; + int has_fini; + __mpu_uint64_t start_entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + + /* Final PROGRAM subsection placement. */ + int init_present; + __mpu_uint64_t init_offset, init_size, init_align; + int text_present; + __mpu_uint64_t text_offset, text_size, text_align; + int fini_present; + __mpu_uint64_t fini_offset, fini_size, fini_align; + + /* Final data-like section placement in its architectural segment. */ + int data_present; + __mpu_uint64_t data_offset, data_section_size, data_align; + int bss_present; + __mpu_uint64_t bss_offset, bss_size, bss_align; + int control_present; + __mpu_uint64_t control_offset, control_section_size, control_align; + int input_present; + __mpu_uint64_t input_offset, input_section_size, input_align; + int output_present; + __mpu_uint64_t output_offset, output_section_size, output_align; + int trace_present; + __mpu_uint64_t trace_offset, trace_section_size, trace_align; + + /* ABI-v2/ABI-v3 startup dynamic-link metadata. */ + cpu_dyn_symbol *dyn_symbols; + size_t dyn_symbol_count; + cpu_dyn_reloc *dyn_relocs; + size_t dyn_reloc_count; +} cpu_asm_image; + +/* Logical input-section identifiers used between assembler, MELF reader and + static linker. Data-like values intentionally match PTR64 selectors. + PROGRAM subsections use private non-selector IDs so .init/.text/.fini stay + distinct until the final PROGRAM layout is assigned by mcpu-ld. */ +#define CPU_OBJ_SEC_DATA CPU_PTR_SEG_DATA +#define CPU_OBJ_SEC_CONTROL CPU_PTR_SEG_CONTROL +#define CPU_OBJ_SEC_INPUT CPU_PTR_SEG_INPUT +#define CPU_OBJ_SEC_OUTPUT CPU_PTR_SEG_OUTPUT +#define CPU_OBJ_SEC_TRACE CPU_PTR_SEG_TRACE +#define CPU_OBJ_SEC_INIT 0x20U +#define CPU_OBJ_SEC_TEXT 0x21U +#define CPU_OBJ_SEC_FINI 0x22U +#define CPU_OBJ_SEC_BSS 0x23U +#define CPU_OBJ_SEC_UNDEF 0xffU + +static inline int +cpu_obj_sec_is_program( unsigned s ) +{ + return( s == CPU_OBJ_SEC_INIT || s == CPU_OBJ_SEC_TEXT || s == CPU_OBJ_SEC_FINI ); +} + +static inline unsigned +cpu_obj_sec_segment( unsigned s ) +{ + if( cpu_obj_sec_is_program( s ) ) return( CPU_PTR_SEG_PROGRAM ); + if( s == CPU_OBJ_SEC_BSS ) return( CPU_PTR_SEG_DATA ); + return( s ); +} + +#define CPU_OBJ_BIND_LOCAL 0U +#define CPU_OBJ_BIND_GLOBAL 1U +#define CPU_OBJ_BIND_WEAK 2U + +#define CPU_OBJ_TYPE_NOTYPE 0U +#define CPU_OBJ_TYPE_OBJECT 1U +#define CPU_OBJ_TYPE_FUNC 2U + +typedef enum cpu_reloc_type +{ + CPU_RELOC_CODE64 = 1, + CPU_RELOC_DATA64 = 2, + CPU_RELOC_PTR64_CODE = 3, + CPU_RELOC_PTR64_DATA = 4, + CPU_RELOC_CPTR64_DATA = 5 +} cpu_reloc_type; + +typedef struct cpu_asm_symbol +{ + char *name; + unsigned int section; + unsigned int binding; + unsigned int type; + __mpu_uint64_t value; /* section-relative byte offset */ + __mpu_uint64_t size; /* zero when unknown */ +} cpu_asm_symbol; + +typedef struct cpu_asm_reloc +{ + unsigned int section; /* logical source section */ + unsigned int type; + size_t symbol_index; + __mpu_uint64_t offset; /* source-section-relative field offset */ + __mpu_int64_t addend; +} cpu_asm_reloc; + +typedef struct cpu_asm_object +{ + __mpu_UNIT_t *init; + size_t init_size; + __mpu_uint64_t init_align; + int has_init; + __mpu_UNIT_t *text; + size_t text_size; + __mpu_uint64_t text_align; + int has_text; + __mpu_UNIT_t *fini; + size_t fini_size; + __mpu_uint64_t fini_align; + int has_fini; + + __mpu_UNIT_t *data; + size_t data_size; + __mpu_uint64_t data_align; + int has_data; + size_t bss_size; + __mpu_uint64_t bss_align; + int has_bss; + __mpu_UNIT_t *control; + size_t control_size; + __mpu_uint64_t control_align; + int has_control; + __mpu_UNIT_t *input; + size_t input_size; + __mpu_uint64_t input_align; + int has_input; + __mpu_UNIT_t *output; + size_t output_size; + __mpu_uint64_t output_align; + int has_output; + __mpu_UNIT_t *trace; + size_t trace_size; + __mpu_uint64_t trace_align; + int has_trace; + + /* Compatibility concatenation for legacy in-process assembler tests only. + It is never serialized as a MELF section. */ + __mpu_UNIT_t *code; + size_t code_size; + __mpu_uint64_t code_align; + + cpu_asm_symbol *symbols; + size_t symbol_count; + cpu_asm_reloc *relocs; + size_t reloc_count; +} cpu_asm_object; + +int cpu_assemble_text( const char *source, cpu_asm_image *image, char *err, size_t errsz ); +void cpu_asm_image_free( cpu_asm_image *image ); + +int cpu_assemble_object( const char *source, cpu_asm_object *object, char *err, size_t errsz ); +void cpu_asm_object_free( cpu_asm_object *object ); + +#endif diff --git a/utils/mcpu-config.c b/utils/mcpu-config.c new file mode 100644 index 0000000..2d44d27 --- /dev/null +++ b/utils/mcpu-config.c @@ -0,0 +1,704 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmpuio.h> + +#include "mcpu-config.h" + +typedef struct mcpu_text mcpu_text; +struct mcpu_text +{ + __mpu_char16_t *data; + size_t length; + size_t capacity; +}; + +static void +mcpu_text_init( mcpu_text *text ) +{ + if( text == NULL ) + return; + + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static void +mcpu_text_free( mcpu_text *text ) +{ + if( text == NULL ) + return; + + free( text->data ); + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static int +mcpu_text_reserve( mcpu_text *text, size_t need ) +{ + __mpu_char16_t *p; + size_t capacity; + + if( text == NULL ) + return( -1 ); + + if( need <= text->capacity ) + return( 0 ); + + capacity = text->capacity ? text->capacity : 256; + while( capacity < need ) + { + if( capacity > SIZE_MAX / 2 ) + { + errno = EOVERFLOW; + return( -1 ); + } + capacity *= 2; + } + + if( capacity > SIZE_MAX / sizeof(__mpu_char16_t) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (__mpu_char16_t *)realloc( text->data, + capacity * sizeof(__mpu_char16_t) ); + if( p == NULL ) + return( -1 ); + + text->data = p; + text->capacity = capacity; + return( 0 ); +} + +static int +mcpu_text_append( mcpu_text *text, + const __mpu_char16_t *data, size_t length ) +{ + if( text == NULL || (data == NULL && length != 0) ) + { + errno = EINVAL; + return( -1 ); + } + + if( length > SIZE_MAX - text->length - 1 ) + { + errno = EOVERFLOW; + return( -1 ); + } + + if( mcpu_text_reserve(text, text->length + length + 1) != 0 ) + return( -1 ); + + if( length != 0 ) + memcpy( text->data + text->length, data, + length * sizeof(__mpu_char16_t) ); + + text->length += length; + text->data[text->length] = 0; + return( 0 ); +} + +static int +mcpu_text_from_utf8( mcpu_text *text, const char *s ) +{ + __mpu_char16_t *u; + __mpu_size_t n; + int rc; + + if( text == NULL || s == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + mcpu_text_free( text ); + mcpu_text_init( text ); + + n = mpu_utf8_to_ucs2( NULL, (const __mpu_char8_t *)s, 0 ); + if( n == (__mpu_size_t)-1 ) + return( -1 ); + + u = (__mpu_char16_t *)calloc( (size_t)n + 1, sizeof(__mpu_char16_t) ); + if( u == NULL ) + return( -1 ); + + if( mpu_utf8_to_ucs2(u, (const __mpu_char8_t *)s, + (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( u ); + return( -1 ); + } + + rc = mcpu_text_append( text, u, (size_t)n ); + free( u ); + return( rc ); +} + +static char * +mcpu_text_to_utf8( const __mpu_char16_t *data, size_t length ) +{ + __mpu_char16_t *tmp; + __mpu_char8_t *out; + __mpu_size_t n; + + if( data == NULL && length != 0 ) + { + errno = EINVAL; + return( NULL ); + } + + tmp = (__mpu_char16_t *)calloc( length + 1, sizeof(__mpu_char16_t) ); + if( tmp == NULL ) + return( NULL ); + + if( length != 0 ) + memcpy( tmp, data, length * sizeof(__mpu_char16_t) ); + + n = mpu_ucs2_to_utf8( NULL, tmp, 0 ); + if( n == (__mpu_size_t)-1 ) + { + free( tmp ); + return( NULL ); + } + + out = (__mpu_char8_t *)calloc( (size_t)n + 1, 1 ); + if( out == NULL ) + { + free( tmp ); + return( NULL ); + } + + if( mpu_ucs2_to_utf8(out, tmp, (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( out ); + free( tmp ); + return( NULL ); + } + + free( tmp ); + return( (char *)out ); +} + + +static char * +read_bytes( const char *filename, size_t *file_length ) +{ + FILE *fp; + char *data; + long size; + size_t n; + + if( file_length == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + *file_length = 0; + + fp = fopen( filename, "rb" ); + if( fp == NULL ) + return( NULL ); + + if( fseek( fp, 0, SEEK_END ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + size = ftell( fp ); + if( size < 0 || fseek( fp, 0, SEEK_SET ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + data = (char *)calloc( (size_t)size + 1, 1 ); + if( data == NULL ) + { + fclose( fp ); + return( NULL ); + } + + n = fread( data, 1, (size_t)size, fp ); + if( n != (size_t)size && ferror(fp) ) + { + free( data ); + fclose( fp ); + return( NULL ); + } + + data[n] = 0; + fclose( fp ); + *file_length = n; + + return( data ); +} + +static int +is_space16( __mpu_char16_t c ) +{ + return( c == ' ' || c == '\t' || c == '\r' || c == '\n' || + c == '\f' || c == '\v' ); +} + +static int +is_name16( __mpu_char16_t c ) +{ + return( (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' ); +} + +static void +skip_space_and_comments( const __mpu_char16_t *s, size_t n, size_t *pos ) +{ + size_t p = *pos; + + for( ;; ) + { + while( p < n && is_space16(s[p]) ) + ++p; + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '*' ) + { + p += 2; + while( p + 1 < n && !(s[p] == '*' && s[p + 1] == '/') ) + ++p; + if( p + 1 < n ) + p += 2; + continue; + } + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '/' ) + { + p += 2; + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + if( p < n && s[p] == '#' ) + { + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + break; + } + + *pos = p; +} + +static char * +expand_value( const mcpu_config *config, const char *value ) +{ + size_t length = strlen( value ); + size_t capacity = length + 64; + char *out = (char *)calloc( capacity, 1 ); + size_t i = 0; + size_t o = 0; + + if( out == NULL ) + return( NULL ); + + while( i < length ) + { + if( value[i] == '$' ) + { + char name[256]; + size_t ni = 0; + size_t j = i + 1; + int braced = 0; + const char *replacement; + + if( j < length && value[j] == '{' ) + { + braced = 1; + ++j; + } + + while( j < length && ni + 1 < sizeof(name) && + ((value[j] >= 'a' && value[j] <= 'z') || + (value[j] >= 'A' && value[j] <= 'Z') || + (value[j] >= '0' && value[j] <= '9') || value[j] == '_') ) + name[ni++] = value[j++]; + + if( braced ) + { + if( j >= length || value[j] != '}' ) + { + free( out ); + errno = EINVAL; + return( NULL ); + } + ++j; + } + + if( ni == 0 ) + { + out[o++] = value[i++]; + continue; + } + + name[ni] = 0; + replacement = mcpu_config_get( config, name ); + if( replacement == NULL ) + replacement = getenv( name ); + if( replacement == NULL ) + replacement = ""; + + { + size_t rl = strlen( replacement ); + if( o + rl + 1 > capacity ) + { + char *p; + while( o + rl + 1 > capacity ) + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + memcpy( out + o, replacement, rl ); + o += rl; + } + + i = j; + continue; + } + + if( o + 2 > capacity ) + { + char *p; + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + + out[o++] = value[i++]; + } + + out[o] = 0; + return( out ); +} + +void +mcpu_config_init( mcpu_config *config ) +{ + if( config == NULL ) + return; + + config->first = NULL; + config->verbose = 0; +} + +void +mcpu_config_free( mcpu_config *config ) +{ + mcpu_config_entry *p; + + if( config == NULL ) + return; + + p = config->first; + while( p ) + { + mcpu_config_entry *next = p->next; + free( p->name ); + free( p->value ); + free( p ); + p = next; + } + + config->first = NULL; +} + +const char * +mcpu_config_get( const mcpu_config *config, const char *name ) +{ + mcpu_config_entry *p; + + if( config == NULL || name == NULL ) + return( NULL ); + + for( p = config->first; p; p = p->next ) + if( strcmp( p->name, name ) == 0 ) + return( p->value ); + + return( NULL ); +} + +int +mcpu_config_set( mcpu_config *config, const char *name, + const char *value ) +{ + mcpu_config_entry *p; + char *new_value; + + if( config == NULL || name == NULL || value == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + new_value = expand_value( config, value ); + if( new_value == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + { + if( strcmp( p->name, name ) == 0 ) + { + free( p->value ); + p->value = new_value; + return( 0 ); + } + } + + p = (mcpu_config_entry *)calloc( 1, sizeof(*p) ); + if( p == NULL ) + { + free( new_value ); + return( -1 ); + } + + p->name = strdup( name ); + if( p->name == NULL ) + { + free( new_value ); + free( p ); + return( -1 ); + } + + p->value = new_value; + p->next = config->first; + config->first = p; + + return( 0 ); +} + + +static int +config_entry_compare( const void *a, const void *b ) +{ + const mcpu_config_entry *ea = *(const mcpu_config_entry * const *)a; + const mcpu_config_entry *eb = *(const mcpu_config_entry * const *)b; + return( strcmp(ea->name, eb->name) ); +} + + +int +mcpu_config_dump( const mcpu_config *config, FILE *stream ) +{ + const mcpu_config_entry *p; + mcpu_config_entry **list; + size_t count = 0; + size_t i = 0; + + if( config == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( p = config->first; p; p = p->next ) + ++count; + + list = count ? (mcpu_config_entry **)calloc( count, sizeof(*list) ) : NULL; + if( count != 0 && list == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + list[i++] = (mcpu_config_entry *)p; + + if( count > 1 ) + qsort( list, count, sizeof(*list), config_entry_compare ); + + for( i = 0; i < count; ++i ) + { + if( fprintf(stream, "%s = %s;\n", list[i]->name, list[i]->value) < 0 ) + { + free( list ); + return( -1 ); + } + } + + free( list ); + return( ferror(stream) ? -1 : 0 ); +} + + +int +mcpu_config_read( mcpu_config *config, const char *filename, + int missing_is_error ) +{ + char *bytes; + size_t byte_length; + mcpu_text text; + size_t p = 0; + int rc = -1; + + if( config == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + bytes = read_bytes( filename, &byte_length ); + if( bytes == NULL ) + { + if( !missing_is_error && errno == ENOENT ) + return( 0 ); + return( -1 ); + } + + mcpu_text_init( &text ); + { + const char *input = bytes; + + if( memchr( bytes, 0, byte_length ) != NULL ) + { + fprintf( stderr, "%s: NUL character is not allowed in configuration text\n", + filename ); + free( bytes ); + return( -1 ); + } + + if( byte_length >= 3 && + (unsigned char)bytes[0] == 0xef && + (unsigned char)bytes[1] == 0xbb && + (unsigned char)bytes[2] == 0xbf ) + input += 3; + + if( mcpu_text_from_utf8( &text, input ) != 0 ) + { + fprintf( stderr, "%s: invalid UTF-8 or non-UCS-2 character\n", + filename ); + free( bytes ); + return( -1 ); + } + } + free( bytes ); + + while( p < text.length ) + { + size_t name_start; + size_t name_end; + size_t value_start; + size_t value_end; + char *name = NULL; + char *value = NULL; + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length ) + break; + + name_start = p; + while( p < text.length && is_name16(text.data[p]) ) + ++p; + name_end = p; + + if( name_end == name_start ) + { + fprintf( stderr, "%s: malformed configuration variable\n", filename ); + goto done; + } + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length || text.data[p] != '=' ) + { + fprintf( stderr, "%s: '=' expected after configuration variable\n", filename ); + goto done; + } + ++p; + + while( p < text.length && is_space16(text.data[p]) ) + ++p; + + value_start = p; + if( p < text.length && (text.data[p] == '"' || text.data[p] == '\'') ) + { + __mpu_char16_t quote = text.data[p++]; + value_start = p; + while( p < text.length && text.data[p] != quote ) + { + if( text.data[p] == '\\' && p + 1 < text.length ) + p += 2; + else + ++p; + } + if( p >= text.length ) + { + fprintf( stderr, "%s: unterminated quoted configuration value\n", filename ); + goto done; + } + value_end = p++; + while( p < text.length && is_space16(text.data[p]) ) + ++p; + } + else + { + while( p < text.length && text.data[p] != ';' && text.data[p] != '\n' ) + ++p; + value_end = p; + while( value_end > value_start && is_space16(text.data[value_end - 1]) ) + --value_end; + } + + if( p >= text.length || text.data[p] != ';' ) + { + fprintf( stderr, "%s: ';' expected after configuration value\n", filename ); + goto done; + } + ++p; + + name = mcpu_text_to_utf8( text.data + name_start, name_end - name_start ); + value = mcpu_text_to_utf8( text.data + value_start, value_end - value_start ); + if( name == NULL || value == NULL ) + { + free( name ); + free( value ); + goto done; + } + + if( mcpu_config_set( config, name, value ) != 0 ) + { + free( name ); + free( value ); + goto done; + } + + free( name ); + free( value ); + name = NULL; + value = NULL; + } + + rc = 0; + +done: + mcpu_text_free( &text ); + return( rc ); +} diff --git a/utils/mcpu-config.h b/utils/mcpu-config.h new file mode 100644 index 0000000..5d9ab0e --- /dev/null +++ b/utils/mcpu-config.h @@ -0,0 +1,30 @@ +#ifndef __MCPU_CONFIG_H__ +#define __MCPU_CONFIG_H__ 1 + +#include <stdio.h> + +typedef struct mcpu_config_entry mcpu_config_entry; +struct mcpu_config_entry +{ + char *name; + char *value; + mcpu_config_entry *next; +}; + +typedef struct mcpu_config mcpu_config; +struct mcpu_config +{ + mcpu_config_entry *first; + int verbose; +}; + +void mcpu_config_init( mcpu_config *config ); +void mcpu_config_free( mcpu_config *config ); +int mcpu_config_read( mcpu_config *config, const char *filename, + int missing_is_error ); +const char *mcpu_config_get( const mcpu_config *config, const char *name ); +int mcpu_config_set( mcpu_config *config, const char *name, + const char *value ); +int mcpu_config_dump( const mcpu_config *config, FILE *stream ); + +#endif /* __MCPU_CONFIG_H__ */ diff --git a/utils/mcpu-dependency.c b/utils/mcpu-dependency.c new file mode 100644 index 0000000..a7d12a8 --- /dev/null +++ b/utils/mcpu-dependency.c @@ -0,0 +1,487 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <mcpu-elf.h> + +#include "mcpu-dependency.h" +#include "mcpu-dyn.h" +#include "mcpu-exec.h" + + +typedef struct dependency_node dependency_node; +struct dependency_node +{ + char *path; + cpu_dyn_dependency_info info; +}; + + +typedef struct dependency_graph dependency_graph; +struct dependency_graph +{ + const mcpu_tool_options *options; + char **preload_path; + cpu_dyn_dependency_info *preload_info; + size_t preload_count; + dependency_node *node; + size_t node_count; + size_t node_capacity; + char *err; + size_t errsz; +}; + + +static void +free_string_vector( char **v, size_t n ) +{ + while( n != 0 ) + free( v[--n] ); + free( v ); + +} /* End of free_string_vector() */ + + +static int +split_path_list( const char *list, char ***result, size_t *count, + char *err, size_t errsz ) +{ + const char *begin; + char **v = NULL; + size_t n = 0, capacity = 0; + + if( result == NULL || count == NULL ) + { + if( err != NULL && errsz != 0 ) + snprintf( err, errsz, "invalid module-list arguments" ); + return( -1 ); + } + + *result = NULL; + *count = 0; + if( list == NULL || *list == 0 ) + return( 0 ); + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *copy; + + if( length == 0 ) + { + snprintf( err, errsz, "empty path in resolved module list" ); + goto bad; + } + + if( n == capacity ) + { + size_t new_capacity = capacity ? capacity * 2U : 4U; + char **p; + + if( new_capacity < capacity || + new_capacity > SIZE_MAX / sizeof(*p) ) + { + snprintf( err, errsz, "too many resolved modules" ); + goto bad; + } + p = (char **)realloc( v, new_capacity * sizeof(*p) ); + if( p == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto bad; + } + v = p; + capacity = new_capacity; + } + + copy = (char *)malloc( length + 1U ); + if( copy == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto bad; + } + memcpy( copy, begin, length ); + copy[length] = 0; + v[n++] = copy; + + if( colon == NULL ) + break; + begin = colon + 1; + } + + *result = v; + *count = n; + return( 0 ); + +bad: + free_string_vector( v, n ); + return( -1 ); + +} /* End of split_path_list() */ + + +static void +dependency_graph_free( dependency_graph *graph ) +{ + size_t i; + + if( graph == NULL ) + return; + + for( i = 0; i < graph->preload_count; ++i ) + { + free( graph->preload_path[i] ); + cpu_dyn_dependency_free( &graph->preload_info[i] ); + } + free( graph->preload_path ); + free( graph->preload_info ); + + for( i = 0; i < graph->node_count; ++i ) + { + free( graph->node[i].path ); + cpu_dyn_dependency_free( &graph->node[i].info ); + } + free( graph->node ); + memset( graph, 0, sizeof(*graph) ); + +} /* End of dependency_graph_free() */ + + +static int +preload_has_soname( const dependency_graph *graph, const char *soname ) +{ + size_t i; + + for( i = graph->preload_count; i != 0; --i ) + { + const cpu_dyn_dependency_info *info = &graph->preload_info[i - 1U]; + + if( info->soname != NULL && strcmp(info->soname, soname) == 0 ) + return( 1 ); + } + return( 0 ); + +} /* End of preload_has_soname() */ + + +static dependency_node * +find_node( dependency_graph *graph, const char *soname ) +{ + size_t i; + + for( i = 0; i < graph->node_count; ++i ) + if( graph->node[i].info.soname != NULL && + strcmp(graph->node[i].info.soname, soname) == 0 ) + return( &graph->node[i] ); + + return( NULL ); + +} /* End of find_node() */ + + +static dependency_node * +add_node( dependency_graph *graph, char *path, + cpu_dyn_dependency_info *info ) +{ + dependency_node *node; + + if( graph->node_count == graph->node_capacity ) + { + size_t capacity = graph->node_capacity ? + graph->node_capacity * 2U : 8U; + dependency_node *p; + + if( capacity < graph->node_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( NULL ); + } + p = (dependency_node *)realloc( graph->node, + capacity * sizeof(*p) ); + if( p == NULL ) + return( NULL ); + graph->node = p; + graph->node_capacity = capacity; + } + + node = &graph->node[graph->node_count++]; + memset( node, 0, sizeof(*node) ); + node->path = path; + node->info = *info; + memset( info, 0, sizeof(*info) ); + return( node ); + +} /* End of add_node() */ + + +static int +enqueue_needed( dependency_graph *graph, const char *soname ) +{ + dependency_node *node; + cpu_dyn_dependency_info info; + char *path = NULL; + + if( preload_has_soname(graph, soname) ) + return( 0 ); + + node = find_node( graph, soname ); + if( node != NULL ) + return( 0 ); + + if( mcpu_tool_resolve_library(graph->options, soname, &path) != 0 ) + { + snprintf( graph->err, graph->errsz, + "cannot resolve DT_NEEDED '%s': %s", soname, strerror(errno) ); + return( -1 ); + } + + memset( &info, 0, sizeof(info) ); + if( cpu_dyn_dependency_read(path, 0, &info, + graph->err, graph->errsz) != 0 ) + { + free( path ); + return( -1 ); + } + + if( info.abi != MELF_ABIVERSION_DYNAMIC_NEEDED || info.soname == NULL ) + { + snprintf( graph->err, graph->errsz, + "%s: DT_NEEDED '%s' did not resolve to an ABI-v3 library", + path, soname ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + if( strcmp(info.soname, soname) != 0 ) + { + snprintf( graph->err, graph->errsz, + "%s: DT_NEEDED '%s' resolved to SONAME '%s'", + path, soname, info.soname ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + + node = add_node( graph, path, &info ); + if( node == NULL ) + { + snprintf( graph->err, graph->errsz, "out of memory" ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + + return( 0 ); + +} /* End of enqueue_needed() */ + + +static int +inspect_preloads( dependency_graph *graph, const char *resolved_preload ) +{ + size_t i; + + if( split_path_list(resolved_preload, &graph->preload_path, + &graph->preload_count, graph->err, graph->errsz) != 0 ) + return( -1 ); + + if( graph->preload_count == 0 ) + return( 0 ); + + graph->preload_info = (cpu_dyn_dependency_info *)calloc( + graph->preload_count, sizeof(*graph->preload_info) ); + if( graph->preload_info == NULL ) + { + snprintf( graph->err, graph->errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < graph->preload_count; ++i ) + if( cpu_dyn_dependency_read(graph->preload_path[i], 0, + &graph->preload_info[i], + graph->err, graph->errsz) != 0 ) + return( -1 ); + + return( 0 ); + +} /* End of inspect_preloads() */ + + +static int +append_load_path( char **buffer, size_t *length, size_t *capacity, + const char *path ) +{ + size_t path_length = strlen( path ); + size_t add = path_length + (*length != 0 ? 1U : 0U); + char *p; + + if( strchr(path, ':') != NULL ) + { + errno = EINVAL; + return( -1 ); + } + if( add > SIZE_MAX - *length - 1U ) + { + errno = EOVERFLOW; + return( -1 ); + } + + if( *length + add + 1U > *capacity ) + { + size_t new_capacity = *capacity ? *capacity : 128U; + + while( new_capacity < *length + add + 1U ) + { + if( new_capacity > SIZE_MAX / 2U ) + { + errno = EOVERFLOW; + return( -1 ); + } + new_capacity *= 2U; + } + p = (char *)realloc( *buffer, new_capacity ); + if( p == NULL ) + return( -1 ); + *buffer = p; + *capacity = new_capacity; + } + + if( *length != 0 ) + (*buffer)[(*length)++] = ':'; + memcpy( *buffer + *length, path, path_length ); + *length += path_length; + (*buffer)[*length] = 0; + return( 0 ); + +} /* End of append_load_path() */ + + +static int +build_load_list( dependency_graph *graph, char **load_list ) +{ + char *buffer = NULL; + size_t length = 0, capacity = 0, i; + + for( i = 0; i < graph->node_count; ++i ) + if( append_load_path(&buffer, &length, &capacity, + graph->node[i].path) != 0 ) + goto bad; + + /* Explicit preloads remain after normal dependencies in their original + command-line order. The loader receives preload_count separately and + applies the ABI-v3 scope policy explicitly rather than inferring it from + this flattened transport list. */ + for( i = 0; i < graph->preload_count; ++i ) + if( append_load_path(&buffer, &length, &capacity, + graph->preload_path[i]) != 0 ) + goto bad; + + *load_list = buffer; + return( 0 ); + +bad: + free( buffer ); + snprintf( graph->err, graph->errsz, + "cannot build runtime module list: %s", strerror(errno) ); + return( -1 ); + +} /* End of build_load_list() */ + + +int +mcpu_dependency_build_load_list( const mcpu_tool_options *options, + const char *executable, + const char *resolved_preload, + char **load_list, + size_t *preload_count, + char *err, size_t errsz ) +{ + dependency_graph graph; + cpu_dyn_dependency_info main_info; + unsigned abi; + size_t i, j; + int rc = -1; + + if( options == NULL || executable == NULL || load_list == NULL || + preload_count == NULL || err == NULL || errsz == 0 ) + return( -1 ); + + *load_list = NULL; + *preload_count = 0; + memset( &graph, 0, sizeof(graph) ); + memset( &main_info, 0, sizeof(main_info) ); + graph.options = options; + graph.err = err; + graph.errsz = errsz; + + if( cpu_exec_abi_version(executable, &abi, err, errsz) != 0 ) + goto done; + + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( resolved_preload != NULL && *resolved_preload != 0 ) + { + *load_list = strdup( resolved_preload ); + if( *load_list == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto done; + } + } + rc = 0; + goto done; + } + + if( inspect_preloads(&graph, resolved_preload) != 0 ) + goto done; + + if( cpu_dyn_dependency_read(executable, 1, &main_info, + err, errsz) != 0 ) + goto done; + + /* Seed the breadth-first scope with every direct dependency before + descending into any transitive dependency. Main DT_NEEDED entries come + first in their recorded order; explicit-preload dependencies follow in + command-line order. */ + for( i = 0; i < main_info.needed_count; ++i ) + if( enqueue_needed(&graph, main_info.needed[i]) != 0 ) + goto done; + + for( i = 0; i < graph.preload_count; ++i ) + for( j = 0; j < graph.preload_info[i].needed_count; ++j ) + if( enqueue_needed(&graph, graph.preload_info[i].needed[j]) != 0 ) + goto done; + + /* node[] is a queue. Appending children while walking it yields a stable + breadth-first dependency order, naturally deduplicating cycles by SONAME. */ + for( i = 0; i < graph.node_count; ++i ) + for( j = 0; j < graph.node[i].info.needed_count; ++j ) + if( enqueue_needed(&graph, graph.node[i].info.needed[j]) != 0 ) + goto done; + + if( build_load_list(&graph, load_list) != 0 ) + goto done; + + *preload_count = graph.preload_count; + rc = 0; + +done: + cpu_dyn_dependency_free( &main_info ); + dependency_graph_free( &graph ); + if( rc != 0 ) + { + free( *load_list ); + *load_list = NULL; + } + return( rc ); + +} /* End of mcpu_dependency_build_load_list() */ diff --git a/utils/mcpu-dependency.h b/utils/mcpu-dependency.h new file mode 100644 index 0000000..aa6fbf5 --- /dev/null +++ b/utils/mcpu-dependency.h @@ -0,0 +1,15 @@ +#ifndef __MCPU_DEPENDENCY_H__ +#define __MCPU_DEPENDENCY_H__ 1 + +#include <stddef.h> + +#include "mcpu-tool-options.h" + +int mcpu_dependency_build_load_list( const mcpu_tool_options *options, + const char *executable, + const char *resolved_preload, + char **load_list, + size_t *preload_count, + char *err, size_t errsz ); + +#endif /* __MCPU_DEPENDENCY_H__ */ diff --git a/utils/mcpu-dyn.c b/utils/mcpu-dyn.c new file mode 100644 index 0000000..4d0ab2e --- /dev/null +++ b/utils/mcpu-dyn.c @@ -0,0 +1,2242 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-dyn.h" +#include "mcpu-melf.h" + +typedef struct blob +{ + unsigned char *p; + size_t n, cap; +} blob; +typedef struct xph +{ + __mpu_uint32_t type, flags; + __mpu_uint64_t off, vaddr, paddr, filesz, memsz, maxsz, align; +} xph; +typedef struct xsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags, addr, off, size, align, entsize; + __mpu_uint32_t link, info, nameoff; + unsigned logical; + const unsigned char *payload; + unsigned char *owned; +} xsec; +typedef struct rsh +{ + __mpu_uint32_t name, type; + __mpu_uint64_t flags, addr, off, size; + __mpu_uint32_t link, info; + __mpu_uint64_t align, entsize; +} rsh; +typedef xph rph; + +typedef struct dsym +{ + char *name; + unsigned binding, type; + __mpu_uint16_t shndx; + __mpu_uint64_t value, size; +} dsym; +typedef struct drela +{ + __mpu_uint16_t target_shndx; + __mpu_uint64_t offset; + size_t sym; + unsigned type; + __mpu_int64_t addend; +} drela; +typedef struct dmod +{ + char *path; + unsigned char *file; + size_t file_size; + int data; + int abi; + __mpu_uint16_t etype; + rph *ph; + size_t phnum; + rsh *sh; + size_t shnum; + const unsigned char *shstr; + size_t shstrn; + dsym *sym; + size_t nsym; + drela *rela; + size_t nrela; + __mpu_uint64_t base[8]; + int load_index[8]; + int stacki, heapi; + int lifecycle_seen; + __mpu_uint64_t entry, init, life, fini; + __mpu_uint64_t data_section_begin, data_section_size; + int has_data_section; + char *soname; + char **needed; + size_t nneeded; +} dmod; + +typedef struct winner +{ + const char *name; + size_t mod, sym; + unsigned binding; +} winner; + +static int +grow( blob *b, size_t add ) +{ + size_t need, c; + unsigned char *q; + if( add > SIZE_MAX - b->n ) return( -1 ); + need = b->n + add; + if( need <= b->cap ) return( 0 ); + c = b->cap ? b->cap : 64; + while( c < need ) + { + if( c > SIZE_MAX / 2 ) return( -1 ); + c *= 2; + } + q = (unsigned char *)realloc( b->p, c ); + if( !q ) return( -1 ); + b->p = q; + b->cap = c; + return( 0 ); +} + +static int +bput( blob *b, const void *p, size_t n ) +{ + if( grow( b, n ) ) return( -1 ); + if( n ) memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +bstr( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + if( b->n > UINT32_MAX || n > UINT32_MAX - b->n ) return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( bput( b, s, n ) ); +} + +static __mpu_uint64_t +nzalign( __mpu_uint64_t a ) +{ + return( a ? a : 1 ); +} + +static __mpu_uint64_t +maxu( __mpu_uint64_t a, __mpu_uint64_t b ) +{ + return( a > b ? a : b ); +} + +static int +addu64( __mpu_uint64_t a, __mpu_uint64_t b, __mpu_uint64_t *r ) +{ + if( a > UINT64_MAX - b ) return( -1 ); + *r = a + b; + return( 0 ); +} + +static int +addsigned( __mpu_uint64_t b, __mpu_int64_t a, __mpu_uint64_t *out ) +{ + if( a >= 0 ) + { + __mpu_uint64_t u = (__mpu_uint64_t)a; + if( b > UINT64_MAX - u ) return( -1 ); + *out = b + u; + } + else + { + __mpu_uint64_t u = (__mpu_uint64_t)( - ( a + 1 ) ) + 1; + if( b < u ) return( -1 ); + *out = b - u; + } + return( 0 ); +} + +static int +span( size_t n, __mpu_uint64_t off, __mpu_uint64_t sz ) +{ + return( off <= n && sz <= n - off ); +} + +static const char * +sec_name( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SEC_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_TRACE ); + default: return( 0 ); + } +} + +static const char * +rela_name( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_RELA_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_RELA_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_RELA_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_RELA_DATA ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_RELA_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_RELA_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_RELA_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_RELA_TRACE ); + default: return( 0 ); + } +} + +static __mpu_uint64_t +sec_flags( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SHFLAGS_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SHFLAGS_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SHFLAGS_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SHFLAGS_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SHFLAGS_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SHFLAGS_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static unsigned +sec_seg( unsigned s ) +{ + return( cpu_obj_sec_segment( s ) ); +} + +static int +image_sec( const cpu_asm_image *im, unsigned s, const unsigned char **p, __mpu_uint64_t *off, __mpu_uint64_t *sz, __mpu_uint64_t *al, int *present, int *nobits ) +{ + *p = 0; + *off = *sz = 0; + *al = 1; + *present = 0; + *nobits = 0; + switch( s ) + { + case CPU_OBJ_SEC_INIT: *p = im->code; + *off = im->init_offset; + *sz = im->init_size; + *al = im->init_align; + *present = im->init_present; + break; + case CPU_OBJ_SEC_TEXT: *p = im->code; + *off = im->text_offset; + *sz = im->text_size; + *al = im->text_align; + *present = im->text_present; + break; + case CPU_OBJ_SEC_FINI: *p = im->code; + *off = im->fini_offset; + *sz = im->fini_size; + *al = im->fini_align; + *present = im->fini_present; + break; + case CPU_OBJ_SEC_DATA: *p = im->data; + *off = im->data_offset; + *sz = im->data_section_size; + *al = im->data_align; + *present = im->data_present; + break; + case CPU_OBJ_SEC_BSS: *off = im->bss_offset; + *sz = im->bss_size; + *al = im->bss_align; + *present = im->bss_present; + *nobits = 1; + break; + case CPU_OBJ_SEC_CONTROL: *p = im->control; + *off = im->control_offset; + *sz = im->control_section_size; + *al = im->control_align; + *present = im->control_present; + break; + case CPU_OBJ_SEC_INPUT: *p = im->input; + *off = im->input_offset; + *sz = im->input_section_size; + *al = im->input_align; + *present = im->input_present; + break; + case CPU_OBJ_SEC_OUTPUT: *p = im->output; + *off = im->output_offset; + *sz = im->output_section_size; + *al = im->output_align; + *present = im->output_present; + break; + case CPU_OBJ_SEC_TRACE: *p = im->trace; + *off = im->trace_offset; + *sz = im->trace_section_size; + *al = im->trace_align; + *present = im->trace_present; + break; + default: return( -1 ); + } + if( *p ) * p += *off; + return( 0 ); +} + +static void +put_ph( unsigned char *p, const xph *x, int data ) +{ + memset( p, 0, MELF64_PHDR_SIZE ); + cpu_melf_put32( p + MELF64_PHDR_P_TYPE_OFF, x->type, data ); + cpu_melf_put32( p + MELF64_PHDR_P_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_PHDR_P_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_PHDR_P_VADDR_OFF, x->vaddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_PADDR_OFF, x->paddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_FILESZ_OFF, x->filesz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MEMSZ_OFF, x->memsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MAXSZ_OFF, x->maxsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_ALIGN_OFF, x->align, data ); +} + +static void +put_sh( unsigned char *p, const xsec *x, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, x->nameoff, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, x->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, x->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, x->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, x->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, x->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, x->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, x->entsize, data ); +} + +static void +get_ph( const unsigned char *p, int data, rph *x ) +{ + x->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + x->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + x->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + x->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + x->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + x->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + x->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); +} + +static void +get_sh( const unsigned char *p, int data, rsh *x ) +{ + x->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + x->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + x->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + x->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + x->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + x->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + x->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + x->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +add_load( xph *ph, size_t *np, __mpu_uint64_t *cur, unsigned seg, __mpu_uint64_t filesz, __mpu_uint64_t memsz, __mpu_uint32_t flags, __mpu_uint64_t align, char *err, size_t es ) +{ + int ov = 0; + align = nzalign( align ); + if( !cpu_melf_is_pow2( align ) || filesz > memsz || memsz > CPU_PTR_OFFSET_MASK || filesz > SIZE_MAX ) + { + snprintf( err, es, "invalid dynamic load descriptor" ); + return( -1 ); + } + *cur = cpu_melf_align_up( *cur, align, &ov ); + if( ov ) + { + snprintf( err, es, "dynamic file layout overflow" ); + return( -1 ); + } + ph[*np] = ( xph ) + { + MELF_PT_LOAD, flags, *cur, MELF_MCPU_PTR64( seg, 0 ), 0, filesz, memsz, memsz, align + }; + ++ *np; + if( addu64( *cur, filesz, cur ) ) + { + snprintf( err, es, "dynamic file layout overflow" ); + return( -1 ); + } + return( 0 ); +} + +static int +cpu_dyn_write_version( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, int abi, + const cpu_dyn_metadata *metadata, + char *err, size_t es ) +{ + static const unsigned logicals[] = + { + CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE + }; + xph ph[8]; + size_t np = 0; + int phidx[8] = + { + -1, -1, -1, -1, -1, -1, -1, -1 + }; + xsec sec[40]; + size_t ns = 1, i, j, nlocal = 0; + __mpu_uint16_t secidx[0x24]; + blob shstr = + { + 0 + } + , dynstr = + { + 0 + } + , dynsym = + { + 0 + } + , dynamic = + { + 0 + } + , relab[9] = + { + { + 0 + } + }; + int data = cpu_melf_data_encoding(), ov = 0; + __mpu_uint64_t cur = MELF64_EHDR_SIZE, shoff, total, data_mem; + unsigned char *file = 0, tbuf[MELF_MCPU_TARGET_SIZE], lbuf[MELF_MCPU_LIFECYCLE_SIZE]; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + __mpu_uint16_t dynstridx, dynsymidx, dynamicidx = 0, shstridx; + int have_data; + memset( sec, 0, sizeof( sec ) ); + memset( secidx, 0, sizeof( secidx ) ); + if( !path || !im || ( !shared && !op ) ) + { + snprintf( err, es, "bad dynamic writer arguments" ); + goto fail; + } + + if( abi != MELF_ABIVERSION_DYNAMIC_EXPLICIT && + abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "unsupported dynamic ABI version" ); + goto fail; + } + + if( abi == MELF_ABIVERSION_DYNAMIC_EXPLICIT ) + { + if( metadata != NULL ) + { + snprintf( err, es, "ABI-v2 writer does not accept dependency metadata" ); + goto fail; + } + } + else + { + size_t k; + + if( shared ) + { + if( metadata == NULL || metadata->soname == NULL || + metadata->soname[0] == 0 || strchr( metadata->soname, '/' ) != NULL ) + { + snprintf( err, es, "ABI-v3 ET_DYN requires a logical SONAME" ); + goto fail; + } + } + else if( metadata != NULL && metadata->soname != NULL ) + { + snprintf( err, es, "ABI-v3 ET_EXEC must not define SONAME" ); + goto fail; + } + + if( metadata != NULL ) + for( k = 0; k < metadata->needed_count; ++k ) + { + if( metadata->needed == NULL || metadata->needed[k] == NULL || + metadata->needed[k][0] == 0 || + strchr( metadata->needed[k], '/' ) != NULL ) + { + snprintf( err, es, "ABI-v3 NEEDED name must be logical" ); + goto fail; + } + } + } + + if( shared ) + { + if( im->has_start || im->has_life || im->init_present || im->fini_present || im->control_present || im->input_present || im->output_present || im->trace_present ) + { + snprintf( err, es, "ET_DYN contains forbidden executable/model sections" ); + goto fail; + } + if( !im->text_present && !im->data_present && !im->bss_present ) + { + snprintf( err, es, "empty shared object" ); + goto fail; + } + } + else + { + if( !im->has_start || !im->has_life || !im->text_present || !im->code_size ) + { + snprintf( err, es, "incomplete dynamic executable" ); + goto fail; + } + if( !op->stack_size || op->stack_size % MCPU_STARTUP_ALIGN || op->stack_size > CPU_PTR_OFFSET_MASK || op->stack_size > SIZE_MAX ) + { + snprintf( err, es, "invalid STACK size" ); + goto fail; + } + if( op->have_heap && ( op->heap_size < CPU_HEAP_BEGIN || op->heap_limit < op->heap_size || op->heap_limit > CPU_PTR_OFFSET_MASK || op->heap_size > SIZE_MAX ) ) + { + snprintf( err, es, "invalid HEAP size/limit" ); + goto fail; + } + } + data_mem = im->data_mem_size ? im->data_mem_size : im->data_size; + have_data = ( data_mem || im->data_present || im->bss_present ); + /* count program headers then lay file images */ + np = ( im->code_size ? 1 : 0 ) + ( have_data ? 1 : 0 ) + ( shared ? 0 : ( ( im->control_present || im->control_size ) + ( im->input_present || im->input_size ) + ( im->output_present || im->output_size ) + ( im->trace_present || im->trace_size ) + 1 + ( op->have_heap ? 1 : 0 ) ) ); + if( np > 8 || cur > UINT64_MAX - np *MELF64_PHDR_SIZE ) + { + snprintf( err, es, "too many dynamic program headers" ); + goto fail; + } + cur += np *MELF64_PHDR_SIZE; + np = 0; + if( im->code_size ) + { + __mpu_uint64_t a = maxu( maxu( nzalign( im->init_align ), nzalign( im->text_align ) ), nzalign( im->fini_align ) ); + phidx[MELF_MCPU_SEG_PROGRAM] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_PROGRAM, im->code_size, im->code_size, MELF_PFLAGS_PROGRAM, a, err, es ) ) goto fail; + } + if( have_data ) + { + phidx[MELF_MCPU_SEG_DATA] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_DATA, im->data_size, data_mem, MELF_PFLAGS_DATA, maxu( 1, maxu( nzalign( im->data_align ), nzalign( im->bss_align ) ) ), err, es ) ) goto fail; + } + if( !shared ) + { + if( im->control_present || im->control_size ) + { + phidx[MELF_MCPU_SEG_CONTROL] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_CONTROL, im->control_size, im->control_size, MELF_PFLAGS_CONTROL, nzalign( im->control_align ), err, es ) ) goto fail; + } + if( im->input_present || im->input_size ) + { + phidx[MELF_MCPU_SEG_INPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_INPUT, im->input_size, im->input_size, MELF_PFLAGS_INPUT, nzalign( im->input_align ), err, es ) ) goto fail; + } + if( im->output_present || im->output_size ) + { + phidx[MELF_MCPU_SEG_OUTPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_OUTPUT, im->output_size, im->output_size, MELF_PFLAGS_OUTPUT, nzalign( im->output_align ), err, es ) ) goto fail; + } + if( im->trace_present || im->trace_size ) + { + phidx[MELF_MCPU_SEG_TRACE] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_TRACE, im->trace_size, im->trace_size, MELF_PFLAGS_TRACE, nzalign( im->trace_align ), err, es ) ) goto fail; + } + phidx[MELF_MCPU_SEG_STACK] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_STACK, MELF_PFLAGS_STACK, 0, MELF_MCPU_VADDR_STACK, 0, 0, op->stack_size, op->stack_size, MCPU_STARTUP_ALIGN + }; + if( op->have_heap ) + { + phidx[MELF_MCPU_SEG_HEAP] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_HEAP, MELF_PFLAGS_HEAP, 0, MELF_MCPU_VADDR_HEAP, 0, 0, op->heap_size, op->heap_limit, 16 + }; + } + } + /* allocated sections */ + for( i = 0; i < sizeof( logicals ) / sizeof( logicals[0] ); ++i ) + { + const unsigned char *p; + __mpu_uint64_t so, sz, al; + int pres, nb; + unsigned lg = logicals[i], seg; + if( image_sec( im, lg, &p, &so, &sz, &al, &pres, &nb ) || !pres ) continue; + if( shared && ( lg != CPU_OBJ_SEC_TEXT && lg != CPU_OBJ_SEC_DATA && lg != CPU_OBJ_SEC_BSS ) ) + { + snprintf( err, es, "forbidden ET_DYN section %s", sec_name( lg ) ); + goto fail; + } + seg = sec_seg( lg ); + if( phidx[seg] < 0 ) + { + snprintf( err, es, "section has no load segment" ); + goto fail; + } + secidx[lg] = (__mpu_uint16_t)ns; + sec[ns] = ( xsec ) + { + sec_name( lg ), nb ? MELF_SHT_NOBITS : MELF_SHT_PROGBITS, sec_flags( lg ), MELF_MCPU_PTR64( seg, so ), nb ? ph[phidx[seg]].off + so : ph[phidx[seg]].off + so, sz, nzalign( al ), 0, 0, 0, 0, lg, p, 0 + }; + ++ns; + } + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( tbuf, &mt, data ); + sec[ns++] = ( xsec ) + { + MELF_SEC_MCPU_TARGET, MELF_SHT_MCPU_TARGET, 0, 0, 0, MELF_MCPU_TARGET_SIZE, 8, MELF_MCPU_TARGET_SIZE, 0, 0, 0, 0, tbuf, 0 + }; + if( !shared ) + { + cpu_melf_fill_lifecycle( &ml ); + cpu_melf_encode_lifecycle( lbuf, &ml, data ); + sec[ns++] = ( xsec ) + { + MELF_SEC_MCPU_LIFECYCLE, MELF_SHT_MCPU_LIFECYCLE, 0, 0, 0, MELF_MCPU_LIFECYCLE_SIZE, 8, MELF_MCPU_LIFECYCLE_SIZE, 0, 0, 0, 0, lbuf, 0 + }; + } + if( bput( &dynstr, "", 1 ) || bput( &dynsym, ( unsigned char[MELF64_SYM_SIZE] ) + { + 0 + } + , MELF64_SYM_SIZE)) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + for( i = 0; i < im->dyn_symbol_count; ++i ) if( im->dyn_symbols[i].binding == MELF_STB_LOCAL )++nlocal; + for( i = 0; i < im->dyn_symbol_count; ++i ) + { + unsigned char sb[MELF64_SYM_SIZE] = + { + 0 + }; + __mpu_uint32_t no; + const cpu_dyn_symbol *d = &im->dyn_symbols[i]; + __mpu_uint16_t shn = MELF_SHN_UNDEF; + if( bstr( &dynstr, d->name, &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + if( d->section != CPU_OBJ_SEC_UNDEF ) + { + if( d->section >= sizeof( secidx ) / sizeof( secidx[0] ) || ! ( shn = secidx[d->section] ) ) + { + snprintf( err, es, "dynamic symbol belongs to absent output section: %s", d->name ); + goto fail; + } + } + cpu_melf_put32( sb + MELF64_SYM_ST_NAME_OFF, no, data ); + sb[MELF64_SYM_ST_INFO_OFF] = MELF64_ST_INFO( d->binding, d->type ); + sb[MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( sb + MELF64_SYM_ST_SHNDX_OFF, shn, data ); + cpu_melf_put64( sb + MELF64_SYM_ST_VALUE_OFF, d->value, data ); + cpu_melf_put64( sb + MELF64_SYM_ST_SIZE_OFF, d->size, data ); + if( bput( &dynsym, sb, sizeof( sb ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + if( abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + size_t k; + + if( metadata != NULL ) + { + if( metadata->soname != NULL ) + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + __mpu_uint32_t no; + + if( bstr( &dynstr, metadata->soname, &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + cpu_melf_put64( db + MELF64_DYN_D_TAG_OFF, + (__mpu_uint64_t)MELF_DT_SONAME, data ); + cpu_melf_put64( db + MELF64_DYN_D_VAL_OFF, no, data ); + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + + for( k = 0; k < metadata->needed_count; ++k ) + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + __mpu_uint32_t no; + + if( bstr( &dynstr, metadata->needed[k], &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + cpu_melf_put64( db + MELF64_DYN_D_TAG_OFF, + (__mpu_uint64_t)MELF_DT_NEEDED, data ); + cpu_melf_put64( db + MELF64_DYN_D_VAL_OFF, no, data ); + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + } + + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + } + + dynstridx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNSTR, MELF_SHT_STRTAB, 0, 0, 0, dynstr.n, 1, 0, 0, 0, 0, 0, dynstr.p, 0 + }; + dynsymidx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNSYM, MELF_SHT_DYNSYM, 0, 0, 0, dynsym.n, 8, MELF64_SYM_SIZE, dynstridx, (__mpu_uint32_t)( nlocal + 1 ), 0, 0, dynsym.p, 0 + }; + if( abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + dynamicidx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNAMIC, MELF_SHT_DYNAMIC, 0, 0, 0, dynamic.n, 8, + MELF64_DYN_SIZE, dynstridx, 0, 0, 0, dynamic.p, 0 + }; + } + for( i = 0; i < im->dyn_reloc_count; ++i ) + { + const cpu_dyn_reloc *r = &im->dyn_relocs[i]; + int li = -1; + for( j = 0; j < sizeof( logicals ) / sizeof( logicals[0] ); ++j ) if( logicals[j] == r->section ) + { + li = (int)j; + break; + } + if( li < 0 || !secidx[r->section] || r->symbol_index >= im->dyn_symbol_count ) + { + snprintf( err, es, "bad retained dynamic relocation" ); + goto fail; + } + unsigned char rb[MELF64_RELA_SIZE] = + { + 0 + }; + cpu_melf_put64( rb + MELF64_RELA_R_OFFSET_OFF, r->offset, data ); + cpu_melf_put64( rb + MELF64_RELA_R_INFO_OFF, MELF64_R_INFO( r->symbol_index + 1, r->type ), data ); + cpu_melf_put64( rb + MELF64_RELA_R_ADDEND_OFF, (__mpu_uint64_t)r->addend, data ); + if( bput( &relab[li], rb, sizeof( rb ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + for( i = 0; i < sizeof( logicals ) / sizeof( logicals[0] ); ++i ) if( relab[i].n ) + { + const char *rn = rela_name( logicals[i] ); + sec[ns++] = ( xsec ) + { + rn, MELF_SHT_RELA, 0, 0, 0, relab[i].n, 8, MELF64_RELA_SIZE, dynsymidx, secidx[logicals[i]], 0, 0, relab[i].p, 0 + }; + } + /* section names */ + if( bput( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + shstridx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_SHSTRTAB, MELF_SHT_STRTAB, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 + }; + for( i = 1; i < ns; ++i ) if( bstr( &shstr, sec[i].name, &sec[i].nameoff ) ) + { + snprintf( err, es, "section string table overflow" ); + goto fail; + } + sec[shstridx].payload = shstr.p; + sec[shstridx].size = shstr.n; + /* non-ALLOC payload layout */ + for( i = 1; i < ns; ++i ) + { + if( sec[i].flags & MELF_SHF_ALLOC ) continue; + cur = cpu_melf_align_up( cur, nzalign( sec[i].align ), &ov ); + if( ov ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + sec[i].off = cur; + if( addu64( cur, sec[i].size, &cur ) ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + } + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov || cur > UINT64_MAX - ns *MELF64_SHDR_SIZE ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + shoff = cur; + total = shoff + ns *MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, es, "dynamic file too large" ); + goto fail; + } + file = (unsigned char *)calloc( (size_t)total, 1 ); + if( !file ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + /* ehdr */ + file[MELF_EI_MAG0] = MELFMAG0; + file[MELF_EI_MAG1] = MELFMAG1; + file[MELF_EI_MAG2] = MELFMAG2; + file[MELF_EI_MAG3] = MELFMAG3; + file[MELF_EI_CLASS] = MELF_CLASS_64; + file[MELF_EI_DATA] = (unsigned char)data; + file[MELF_EI_VERSION] = MELF_EV_CURRENT; + file[MELF_EI_OSABI] = MELF_OSABI_MCPU; + file[MELF_EI_ABIVERSION] = (unsigned char)abi; + cpu_melf_put16( file + MELF64_EHDR_E_TYPE_OFF, shared ? MELF_ET_DYN : MELF_ET_EXEC, data ); + cpu_melf_put16( file + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( file + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + if( !shared ) + { + cpu_melf_put64( file + MELF64_EHDR_E_ENTRY_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->start_entry ), data ); + cpu_melf_put64( file + MELF64_EHDR_E_INIT_ENTRY_OFF, im->has_init ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_entry ) : 0, data ); + cpu_melf_put64( file + MELF64_EHDR_E_LIFE_ENTRY_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->life_entry ), data ); + cpu_melf_put64( file + MELF64_EHDR_E_FINI_ENTRY_OFF, im->has_fini ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_entry ) : 0, data ); + } + cpu_melf_put64( file + MELF64_EHDR_E_PHOFF_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put64( file + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( file + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_PHENTSIZE_OFF, MELF64_PHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_PHNUM_OFF, (__mpu_uint16_t)np, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHNUM_OFF, (__mpu_uint16_t)ns, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHSTRNDX_OFF, shstridx, data ); + for( i = 0; i < np; ++i ) put_ph( file + MELF64_EHDR_SIZE + i *MELF64_PHDR_SIZE, &ph[i], data ); + /* load payloads */ + for( i = 0; i < np; ++i ) if( ph[i].type == MELF_PT_LOAD && ph[i].filesz ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + const unsigned char *p = 0; + if( seg == MELF_MCPU_SEG_PROGRAM ) p = im->code; + else if( seg == MELF_MCPU_SEG_DATA ) p = im->data; + else if( seg == MELF_MCPU_SEG_CONTROL ) p = im->control; + else if( seg == MELF_MCPU_SEG_INPUT ) p = im->input; + else if( seg == MELF_MCPU_SEG_OUTPUT ) p = im->output; + else if( seg == MELF_MCPU_SEG_TRACE ) p = im->trace; + if( !p ) + { + snprintf( err, es, "missing load payload" ); + goto fail; + } + memcpy( file + (size_t)ph[i].off, p, (size_t)ph[i].filesz ); + } + for( i = 1; i < ns; ++i ) if( ! ( sec[i].flags & MELF_SHF_ALLOC ) && sec[i].type != MELF_SHT_NOBITS && sec[i].size && sec[i].payload ) memcpy( file + (size_t)sec[i].off, sec[i].payload, (size_t)sec[i].size ); + for( i = 0; i < ns; ++i ) put_sh( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &sec[i], data ); + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) goto fail; + free( file ); + free( shstr.p ); + free( dynstr.p ); + free( dynsym.p ); + free( dynamic.p ); + for( i = 0; i < 9; ++i ) free( relab[i].p ); + (void)dynamicidx; + return( 0 ); + +fail: + free( file ); + free( shstr.p ); + free( dynstr.p ); + free( dynsym.p ); + free( dynamic.p ); + for( i = 0; i < 9; ++i ) free( relab[i].p ); + return( -1 ); +} + +int +cpu_dyn_write( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, + char *err, size_t es ) +{ + return( cpu_dyn_write_version(path, im, op, shared, + MELF_ABIVERSION_DYNAMIC_EXPLICIT, NULL, + err, es) ); +} + +int +cpu_dyn_write_abi3( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, + const cpu_dyn_metadata *metadata, + char *err, size_t es ) +{ + return( cpu_dyn_write_version(path, im, op, shared, + MELF_ABIVERSION_DYNAMIC_NEEDED, metadata, + err, es) ); +} + +static int +name_at( const unsigned char *t, size_t n, __mpu_uint32_t off, const char **out ) +{ + if( off >= n ) return( -1 ); + *out = (const char *)t + off; + return( memchr( *out, 0, n - off ) ? 0 : -1 ); +} + +static int +expected_pflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_PFLAGS_PROGRAM ); + case MELF_MCPU_SEG_DATA: return( MELF_PFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_PFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_PFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_PFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_PFLAGS_TRACE ); + case MELF_MCPU_SEG_STACK: return( MELF_PFLAGS_STACK ); + case MELF_MCPU_SEG_HEAP: return( MELF_PFLAGS_HEAP ); + default: return( -1 ); + } +} + +static __mpu_uint64_t +expected_shflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_SHFLAGS_TEXT ); + case MELF_MCPU_SEG_DATA: return( MELF_SHFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_SHFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static int +image_seg( unsigned seg ) +{ + return( seg <= MELF_MCPU_SEG_TRACE && seg != MELF_MCPU_SEG_STACK ); +} + +static void +free_mod( dmod *m ) +{ + size_t i; + if( !m ) return; + free( m->path ); + free( m->file ); + free( m->ph ); + free( m->sh ); + for( i = 0; i < m->nsym; ++i ) free( m->sym[i].name ); + free( m->sym ); + free( m->rela ); + free( m->soname ); + for( i = 0; i < m->nneeded; ++i ) free( m->needed[i] ); + free( m->needed ); + memset( m, 0, sizeof( *m ) ); +} + +static int +parse_module( const char *path, int want_exec, int reject_needed, dmod *m, char *err, size_t es ) +{ + __mpu_uint64_t phoff, shoff; + __mpu_uint16_t phnum, shnum, shstridx; + size_t i; + int nt = 0, nl = 0, dynsymidx = -1, dynstridx = -1, dynamicidx = -1; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + memset( m, 0, sizeof( *m ) ); + for( i = 0; i < 8; ++i ) m->load_index[i] = -1; + m->stacki = m->heapi = -1; + m->path = strdup( path ); + if( !m->path ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( cpu_melf_readall( path, &m->file, &m->file_size, err, es ) ) return( -1 ); + if( m->file_size < MELF64_EHDR_SIZE ) + { + snprintf( err, es, "%s: truncated MELF", path ); + return( -1 ); + } + unsigned char *f = m->file; + if( f[MELF_EI_MAG0] != MELFMAG0 || f[MELF_EI_MAG1] != MELFMAG1 || + f[MELF_EI_MAG2] != MELFMAG2 || f[MELF_EI_MAG3] != MELFMAG3 || + f[MELF_EI_CLASS] != MELF_CLASS_64 || + f[MELF_EI_VERSION] != MELF_EV_CURRENT || + f[MELF_EI_OSABI] != MELF_OSABI_MCPU ) + { + snprintf( err, es, "%s: not a supported dynamic MELF file", path ); + return( -1 ); + } + m->abi = f[MELF_EI_ABIVERSION]; + if( m->abi != MELF_ABIVERSION_DYNAMIC_EXPLICIT && + m->abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "%s: unsupported dynamic MELF ABI %d", path, m->abi ); + return( -1 ); + } + m->data = f[MELF_EI_DATA]; + if( m->data != MELF_DATA_LSB && m->data != MELF_DATA_MSB ) + { + snprintf( err, es, "%s: unsupported EI_DATA", path ); + return( -1 ); + } + m->etype = cpu_melf_get16( f + MELF64_EHDR_E_TYPE_OFF, m->data ); + if( ( want_exec && m->etype != MELF_ET_EXEC ) || ( !want_exec && m->etype != MELF_ET_DYN ) || cpu_melf_get16( f + MELF64_EHDR_E_MACHINE_OFF, m->data ) != MELF_EM_MCPU || cpu_melf_get32( f + MELF64_EHDR_E_FLAGS_OFF, m->data ) != 0 || cpu_melf_get16( f + MELF64_EHDR_E_EHSIZE_OFF, m->data ) != MELF64_EHDR_SIZE || cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, m->data ) != MELF64_PHDR_SIZE || cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, m->data ) != MELF64_SHDR_SIZE ) + { + snprintf( err, es, "%s: invalid dynamic MELF header", path ); + return( -1 ); + } + m->entry = cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, m->data ); + m->init = cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, m->data ); + m->life = cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, m->data ); + m->fini = cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, m->data ); + phoff = cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, m->data ); + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, m->data ); + phnum = cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, m->data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, m->data ); + shstridx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, m->data ); + if( !phnum || !shnum || shstridx >= shnum || !span( m->file_size, phoff, (__mpu_uint64_t)phnum *MELF64_PHDR_SIZE ) || !span( m->file_size, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE ) ) + { + snprintf( err, es, "%s: invalid MELF tables", path ); + return( -1 ); + } + m->ph = ( rph * ) calloc( phnum, sizeof( *m->ph ) ); + m->sh = ( rsh * ) calloc( shnum, sizeof( *m->sh ) ); + if( !m->ph || !m->sh ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->phnum = phnum; + m->shnum = shnum; + for( i = 0; i < phnum; ++i ) get_ph( f + (size_t)phoff + i *MELF64_PHDR_SIZE, m->data, &m->ph[i] ); + for( i = 0; i < shnum; ++i ) get_sh( f + (size_t)shoff + i *MELF64_SHDR_SIZE, m->data, &m->sh[i] ); + if( m->sh[shstridx].type != MELF_SHT_STRTAB || !span( m->file_size, m->sh[shstridx].off, m->sh[shstridx].size ) || m->sh[shstridx].size > SIZE_MAX ) + { + snprintf( err, es, "%s: invalid .shstrtab", path ); + return( -1 ); + } + m->shstr = f + (size_t)m->sh[shstridx].off; + m->shstrn = (size_t)m->sh[shstridx].size; + for( i = 0; i < phnum; ++i ) + { + rph *p = &m->ph[i]; + if( p->paddr || ( p->align > 1 && !cpu_melf_is_pow2( p->align ) ) ) + { + snprintf( err, es, "%s: invalid program header", path ); + return( -1 ); + } + if( p->type == MELF_PT_LOAD ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( p->vaddr ); + if( !image_seg( seg ) || MELF_MCPU_PTR64_OFFSET( p->vaddr ) != 0 || m->load_index[seg] >= 0 || p->flags != (unsigned)expected_pflags( seg ) || p->filesz > p->memsz || p->maxsz != p->memsz || !span( m->file_size, p->off, p->filesz ) || p->memsz > CPU_PTR_OFFSET_MASK ) + { + snprintf( err, es, "%s: invalid PT_LOAD", path ); + return( -1 ); + } + if( !want_exec && seg != MELF_MCPU_SEG_PROGRAM && seg != MELF_MCPU_SEG_DATA ) + { + snprintf( err, es, "%s: ET_DYN has forbidden load segment", path ); + return( -1 ); + } + m->load_index[seg] = (int)i; + } + else if( p->type == MELF_PT_MCPU_STACK ) + { + if( !want_exec || m->stacki >= 0 || p->vaddr != MELF_MCPU_VADDR_STACK || p->flags != MELF_PFLAGS_STACK || p->off || p->filesz || !p->memsz || p->maxsz != p->memsz || p->memsz % MCPU_STARTUP_ALIGN ) + { + snprintf( err, es, "%s: invalid STACK descriptor", path ); + return( -1 ); + } + m->stacki = (int)i; + } + else if( p->type == MELF_PT_MCPU_HEAP ) + { + if( !want_exec || m->heapi >= 0 || p->vaddr != MELF_MCPU_VADDR_HEAP || p->flags != MELF_PFLAGS_HEAP || p->off || p->filesz || p->memsz < CPU_HEAP_BEGIN || p->maxsz < p->memsz ) + { + snprintf( err, es, "%s: invalid HEAP descriptor", path ); + return( -1 ); + } + m->heapi = (int)i; + } + else + { + snprintf( err, es, "%s: unsupported program header", path ); + return( -1 ); + } + } + if( want_exec && ( m->load_index[MELF_MCPU_SEG_PROGRAM] < 0 || m->stacki < 0 ) ) + { + snprintf( err, es, "%s: dynamic executable lacks PROGRAM/STACK", path ); + return( -1 ); + } + if( !want_exec && ( m->stacki >= 0 || m->heapi >= 0 || m->entry || m->init || m->life || m->fini ) ) + { + snprintf( err, es, "%s: ET_DYN contains process lifecycle state", path ); + return( -1 ); + } + if( want_exec && m->load_index[MELF_MCPU_SEG_DATA] >= 0 ) + { + rph *dp = &m->ph[m->load_index[MELF_MCPU_SEG_DATA]]; + if( dp->filesz < CPU_DATA_NULL_GUARD_SIZE || dp->memsz < CPU_DATA_NULL_GUARD_SIZE ) + { + snprintf( err, es, "%s: DATA segment lacks process NULL guard", path ); + return( -1 ); + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i ) if( m->file[(size_t)dp->off + i] ) + { + snprintf( err, es, "%s: DATA NULL guard is not zero", path ); + return( -1 ); + } + } + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( m->shstr, m->shstrn, m->sh[i].name, &name ) ) + { + snprintf( err, es, "%s: bad section name", path ); + return( -1 ); + } + if( m->sh[i].type == MELF_SHT_MCPU_TARGET ) + { + if( nt++ || m->sh[i].size != MELF_MCPU_TARGET_SIZE || !span( m->file_size, m->sh[i].off, m->sh[i].size ) || cpu_melf_decode_target( f + (size_t)m->sh[i].off, &mt, m->data ) || cpu_melf_validate_target( &mt, m->data, err, es ) ) return( -1 ); + } + else if( m->sh[i].type == MELF_SHT_MCPU_LIFECYCLE ) + { + if( !want_exec || nl++ || m->sh[i].size != MELF_MCPU_LIFECYCLE_SIZE || !span( m->file_size, m->sh[i].off, m->sh[i].size ) || cpu_melf_decode_lifecycle( f + (size_t)m->sh[i].off, &ml, m->data ) || cpu_melf_validate_lifecycle( &ml, err, es ) ) return( -1 ); + } + else if( m->sh[i].type == MELF_SHT_DYNSYM ) + { + if( dynsymidx >= 0 ) + { + snprintf( err, es, "%s: duplicate .dynsym", path ); + return( -1 ); + } + dynsymidx = (int)i; + } + else if( m->sh[i].type == MELF_SHT_DYNAMIC ) + { + if( dynamicidx >= 0 || strcmp( name, MELF_SEC_DYNAMIC ) ) + { + snprintf( err, es, "%s: invalid or duplicate .dynamic", path ); + return( -1 ); + } + dynamicidx = (int)i; + } + else if( m->sh[i].type == MELF_SHT_STRTAB && !strcmp( name, MELF_SEC_DYNSTR ) ) + { + if( dynstridx >= 0 ) + { + snprintf( err, es, "%s: duplicate .dynstr", path ); + return( -1 ); + } + dynstridx = (int)i; + } + if( m->sh[i].flags & MELF_SHF_ALLOC ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( m->sh[i].addr ), flagseg = MELF_SHF_MCPU_GET_SEG( m->sh[i].flags ); + if( ( m->sh[i].flags & MELF_SHF_MCPU_SEG_VALID ) == 0 || m->load_index[seg] < 0 || seg != flagseg || m->sh[i].flags != expected_shflags( seg ) || m->sh[i].align == 0 || !cpu_melf_is_pow2( m->sh[i].align ) ) + { + snprintf( err, es, "%s: invalid allocated section %s", path, name ); + return( -1 ); + } + if( !want_exec && strcmp( name, MELF_SEC_TEXT ) && strcmp( name, MELF_SEC_DATA ) && strcmp( name, MELF_SEC_BSS ) ) + { + snprintf( err, es, "%s: ET_DYN has forbidden allocated section %s", path, name ); + return( -1 ); + } + __mpu_uint64_t off = MELF_MCPU_PTR64_OFFSET( m->sh[i].addr ); + rph *p = &m->ph[m->load_index[seg]]; + if( off > p->memsz || m->sh[i].size > p->memsz - off ) + { + snprintf( err, es, "%s: section outside PT_LOAD", path ); + return( -1 ); + } + if( m->sh[i].type == MELF_SHT_PROGBITS && ( m->sh[i].off < p->off || m->sh[i].off - p->off != off || m->sh[i].size > p->filesz - off || !span( m->file_size, m->sh[i].off, m->sh[i].size ) ) ) + { + snprintf( err, es, "%s: section/file mapping mismatch", path ); + return( -1 ); + } + if( seg == MELF_MCPU_SEG_DATA && !strcmp( name, MELF_SEC_DATA ) ) + { + m->has_data_section = 1; + m->data_section_begin = off; + m->data_section_size = m->sh[i].size; + } + } + } + if( nt != 1 || nl != ( want_exec ? 1 : 0 ) || + dynsymidx < 0 || dynstridx < 0 ) + { + snprintf( err, es, "%s: required dynamic metadata missing", path ); + return( -1 ); + } + if( m->abi == MELF_ABIVERSION_DYNAMIC_EXPLICIT && dynamicidx >= 0 ) + { + snprintf( err, es, "%s: ABI-v2 file must not contain .dynamic", path ); + return( -1 ); + } + if( m->abi == MELF_ABIVERSION_DYNAMIC_NEEDED && dynamicidx < 0 ) + { + snprintf( err, es, "%s: ABI-v3 file lacks .dynamic", path ); + return( -1 ); + } + if( want_exec ) + { + rph *pp = &m->ph[m->load_index[MELF_MCPU_SEG_PROGRAM]]; + if( !m->entry || !m->life || MELF_MCPU_PTR64_SEG( m->entry ) != MELF_MCPU_SEG_PROGRAM || MELF_MCPU_PTR64_SEG( m->life ) != MELF_MCPU_SEG_PROGRAM || MELF_MCPU_PTR64_OFFSET( m->entry ) >= pp->memsz || MELF_MCPU_PTR64_OFFSET( m->life ) >= pp->memsz || ( m->init && m->init != m->entry ) || ( m->init && MELF_MCPU_PTR64_SEG( m->init ) != MELF_MCPU_SEG_PROGRAM ) || ( m->fini && MELF_MCPU_PTR64_SEG( m->fini ) != MELF_MCPU_SEG_PROGRAM ) ) + { + snprintf( err, es, "%s: invalid lifecycle entries", path ); + return( -1 ); + } + } + rsh *dy = &m->sh[dynsymidx], *ds = &m->sh[dynstridx]; + if( dy->link != (unsigned)dynstridx || dy->entsize != MELF64_SYM_SIZE || dy->size % MELF64_SYM_SIZE || !span( m->file_size, dy->off, dy->size ) || !span( m->file_size, ds->off, ds->size ) || ds->size > SIZE_MAX ) + { + snprintf( err, es, "%s: invalid dynamic symbol tables", path ); + return( -1 ); + } + const unsigned char *str = f + (size_t)ds->off; + size_t strn = (size_t)ds->size; + + if( m->abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + rsh *dd = &m->sh[dynamicidx]; + size_t ndyn, k; + int seen_null = 0; + + if( dd->flags != 0 || dd->addr != 0 || + dd->link != (unsigned)dynstridx || dd->info != 0 || + dd->align != 8 || dd->entsize != MELF64_DYN_SIZE || + dd->size < MELF64_DYN_SIZE || dd->size % MELF64_DYN_SIZE || + !span( m->file_size, dd->off, dd->size ) ) + { + snprintf( err, es, "%s: invalid .dynamic section", path ); + return( -1 ); + } + + ndyn = (size_t)( dd->size / MELF64_DYN_SIZE ); + for( k = 0; k < ndyn; ++k ) + { + const unsigned char *dp = + f + (size_t)dd->off + k * MELF64_DYN_SIZE; + __mpu_int64_t tag = + (__mpu_int64_t)cpu_melf_get64( dp + MELF64_DYN_D_TAG_OFF, m->data ); + __mpu_uint64_t val = + cpu_melf_get64( dp + MELF64_DYN_D_VAL_OFF, m->data ); + const char *nm; + + if( tag == MELF_DT_NULL ) + { + if( val != 0 || k + 1 != ndyn ) + { + snprintf( err, es, "%s: non-canonical DT_NULL", path ); + return( -1 ); + } + seen_null = 1; + continue; + } + + if( seen_null ) + { + snprintf( err, es, "%s: dynamic entry follows DT_NULL", path ); + return( -1 ); + } + + if( tag == MELF_DT_SONAME ) + { + if( want_exec || m->soname != NULL || val > UINT32_MAX || + name_at( str, strn, (__mpu_uint32_t)val, &nm ) || + nm[0] == 0 || strchr( nm, '/' ) != NULL ) + { + snprintf( err, es, "%s: invalid DT_SONAME", path ); + return( -1 ); + } + m->soname = strdup( nm ); + if( m->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + else if( tag == MELF_DT_NEEDED ) + { + char **q; + + if( val > UINT32_MAX || + name_at( str, strn, (__mpu_uint32_t)val, &nm ) || + nm[0] == 0 || strchr( nm, '/' ) != NULL ) + { + snprintf( err, es, "%s: invalid DT_NEEDED", path ); + return( -1 ); + } + if( m->nneeded == SIZE_MAX / sizeof( *q ) ) + { + snprintf( err, es, "too many DT_NEEDED entries" ); + return( -1 ); + } + q = (char **)realloc( m->needed, + (m->nneeded + 1) * sizeof( *q ) ); + if( q == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->needed = q; + m->needed[m->nneeded] = strdup( nm ); + if( m->needed[m->nneeded] == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + ++m->nneeded; + } + else + { + snprintf( err, es, "%s: unsupported dynamic tag %lld", + path, (long long)tag ); + return( -1 ); + } + } + + if( !seen_null ) + { + snprintf( err, es, "%s: .dynamic lacks DT_NULL", path ); + return( -1 ); + } + if( !want_exec && m->soname == NULL ) + { + snprintf( err, es, "%s: ABI-v3 ET_DYN lacks SONAME", path ); + return( -1 ); + } + if( reject_needed && m->nneeded != 0 ) + { + snprintf( err, es, + "%s: ABI-v3 DT_NEEDED loading is not implemented yet", path ); + return( -1 ); + } + } + + m->nsym = (size_t)( dy->size / MELF64_SYM_SIZE ); + m->sym = ( dsym * ) calloc( m->nsym, sizeof( *m->sym ) ); + if( !m->sym ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + for( i = 0; i < m->nsym; ++i ) + { + const unsigned char *sp = f + (size_t)dy->off + i *MELF64_SYM_SIZE; + __mpu_uint32_t no = cpu_melf_get32( sp + MELF64_SYM_ST_NAME_OFF, m->data ); + m->sym[i].binding = MELF64_ST_BIND( sp[MELF64_SYM_ST_INFO_OFF] ); + m->sym[i].type = MELF64_ST_TYPE( sp[MELF64_SYM_ST_INFO_OFF] ); + m->sym[i].shndx = cpu_melf_get16( sp + MELF64_SYM_ST_SHNDX_OFF, m->data ); + m->sym[i].value = cpu_melf_get64( sp + MELF64_SYM_ST_VALUE_OFF, m->data ); + m->sym[i].size = cpu_melf_get64( sp + MELF64_SYM_ST_SIZE_OFF, m->data ); + if( i == 0 ) continue; + const char *nm; + if( name_at( str, strn, no, &nm ) || !no || ( m->sym[i].binding != MELF_STB_LOCAL && m->sym[i].binding != MELF_STB_GLOBAL && m->sym[i].binding != MELF_STB_WEAK ) || sp[MELF64_SYM_ST_OTHER_OFF] != MELF_STV_DEFAULT ) + { + snprintf( err, es, "%s: invalid .dynsym entry", path ); + return( -1 ); + } + m->sym[i].name = strdup( nm ); + if( !m->sym[i].name ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( m->sym[i].shndx != MELF_SHN_UNDEF ) + { + if( m->sym[i].shndx >= shnum || ( m->sh[m->sym[i].shndx].flags & MELF_SHF_ALLOC ) == 0 || m->sym[i].value > m->sh[m->sym[i].shndx].size || m->sym[i].size > m->sh[m->sym[i].shndx].size - m->sym[i].value ) + { + snprintf( err, es, "%s: dynamic symbol outside section", path ); + return( -1 ); + } + } + } + for( i = 1; i < shnum; ++i ) if( m->sh[i].type == MELF_SHT_RELA ) + { + rsh *rs = &m->sh[i]; + if( rs->link != (unsigned)dynsymidx || rs->info >= shnum || rs->entsize != MELF64_RELA_SIZE || rs->size % MELF64_RELA_SIZE || !span( m->file_size, rs->off, rs->size ) || ( m->sh[rs->info].flags & MELF_SHF_ALLOC ) == 0 || m->sh[rs->info].type == MELF_SHT_NOBITS ) + { + snprintf( err, es, "%s: invalid dynamic RELA section", path ); + return( -1 ); + } + size_t nr = (size_t)( rs->size / MELF64_RELA_SIZE ), k; + drela *q = ( drela * ) realloc( m->rela, ( m->nrela + nr ) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->rela = q; + for( k = 0; k < nr; ++k ) + { + const unsigned char *rp = f + (size_t)rs->off + k *MELF64_RELA_SIZE; + __mpu_uint64_t info = cpu_melf_get64( rp + MELF64_RELA_R_INFO_OFF, m->data ); + size_t sy = (size_t)MELF64_R_SYM( info ); + unsigned rt = MELF64_R_TYPE( info ); + __mpu_uint64_t ro = cpu_melf_get64( rp + MELF64_RELA_R_OFFSET_OFF, m->data ); + if( !sy || sy >= m->nsym || rt < CPU_RELOC_CODE64 || rt > CPU_RELOC_CPTR64_DATA || ro > m->sh[rs->info].size || 8 > m->sh[rs->info].size - ro ) + { + snprintf( err, es, "%s: bad runtime relocation", path ); + return( -1 ); + } + m->rela[m->nrela++] = ( drela ) + { + (__mpu_uint16_t)rs->info, ro, sy, rt, (__mpu_int64_t)cpu_melf_get64( rp + MELF64_RELA_R_ADDEND_OFF, m->data ) + }; + } + } + return( 0 ); +} + +void +cpu_dyn_library_free( cpu_dyn_library_info *info ) +{ + size_t i; + + if( info == NULL ) + return; + + free( info->soname ); + info->soname = NULL; + for( i = 0; i < info->symbol_count; ++i ) + free( info->symbols[i].name ); + free( info->symbols ); + info->symbols = NULL; + info->symbol_count = 0; + +} /* End of cpu_dyn_library_free() */ + + +int +cpu_dyn_library_read( const char *path, cpu_dyn_library_info *info, + char *err, size_t es ) +{ + dmod m; + size_t i, n = 0; + int rc = -1; + + if( path == NULL || info == NULL ) + { + if( err != NULL && es != 0 ) + snprintf( err, es, "invalid shared-library inspection arguments" ); + return( -1 ); + } + + memset( info, 0, sizeof( *info ) ); + memset( &m, 0, sizeof( m ) ); + + if( parse_module(path, 0, 0, &m, err, es) != 0 ) + goto done; + + if( m.abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "%s: shared-library dependency requires MELF ABI-v3", + path ); + goto done; + } + if( m.soname == NULL ) + { + snprintf( err, es, "%s: ABI-v3 ET_DYN lacks SONAME", path ); + goto done; + } + + info->soname = strdup( m.soname ); + if( info->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + for( i = 1; i < m.nsym; ++i ) + if( m.sym[i].shndx != MELF_SHN_UNDEF && + ( m.sym[i].binding == MELF_STB_GLOBAL || + m.sym[i].binding == MELF_STB_WEAK ) ) + ++n; + + if( n != 0 ) + { + info->symbols = (cpu_dyn_library_symbol *)calloc( n, + sizeof(*info->symbols) ); + if( info->symbols == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + for( i = 1; i < m.nsym; ++i ) + if( m.sym[i].shndx != MELF_SHN_UNDEF && + ( m.sym[i].binding == MELF_STB_GLOBAL || + m.sym[i].binding == MELF_STB_WEAK ) ) + { + cpu_dyn_library_symbol *s = &info->symbols[info->symbol_count]; + + s->name = strdup( m.sym[i].name ); + if( s->name == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + s->binding = m.sym[i].binding; + s->type = m.sym[i].type; + ++info->symbol_count; + } + } + + rc = 0; + +done: + free_mod( &m ); + if( rc != 0 ) + cpu_dyn_library_free( info ); + return( rc ); + +} /* End of cpu_dyn_library_read() */ + + +int +cpu_dyn_library_find_symbol( const cpu_dyn_library_info *info, + const char *name, + const cpu_dyn_library_symbol **symbol ) +{ + size_t i; + + if( symbol != NULL ) + *symbol = NULL; + if( info == NULL || name == NULL ) + return( -1 ); + + for( i = 0; i < info->symbol_count; ++i ) + if( strcmp(info->symbols[i].name, name) == 0 ) + { + if( symbol != NULL ) + *symbol = &info->symbols[i]; + return( 0 ); + } + + return( -1 ); + +} /* End of cpu_dyn_library_find_symbol() */ + + +void +cpu_dyn_dependency_free( cpu_dyn_dependency_info *info ) +{ + size_t i; + + if( info == NULL ) + return; + + free( info->soname ); + info->soname = NULL; + for( i = 0; i < info->needed_count; ++i ) + free( info->needed[i] ); + free( info->needed ); + info->needed = NULL; + info->needed_count = 0; + info->abi = 0; + +} /* End of cpu_dyn_dependency_free() */ + + +int +cpu_dyn_dependency_read( const char *path, int want_exec, + cpu_dyn_dependency_info *info, + char *err, size_t es ) +{ + dmod m; + size_t i; + int rc = -1; + + if( path == NULL || info == NULL ) + { + if( err != NULL && es != 0 ) + snprintf( err, es, "invalid dynamic dependency inspection arguments" ); + return( -1 ); + } + + memset( info, 0, sizeof(*info) ); + memset( &m, 0, sizeof(m) ); + + if( parse_module(path, want_exec, 0, &m, err, es) != 0 ) + goto done; + + info->abi = m.abi; + if( m.soname != NULL ) + { + info->soname = strdup( m.soname ); + if( info->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + + if( m.nneeded != 0 ) + { + info->needed = (char **)calloc( m.nneeded, sizeof(*info->needed) ); + if( info->needed == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + for( i = 0; i < m.nneeded; ++i ) + { + info->needed[i] = strdup( m.needed[i] ); + if( info->needed[i] == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + ++info->needed_count; + } + } + + rc = 0; + +done: + free_mod( &m ); + if( rc != 0 ) + cpu_dyn_dependency_free( info ); + return( rc ); + +} /* End of cpu_dyn_dependency_read() */ + + +static int +seg_alloc( unsigned seg, __mpu_uint64_t n, __mpu_uint64_t max, __mpu_UNIT_t **dst ) +{ + int r = -1; + *dst = 0; + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: r = __cpu_program_alloc( n ); + *dst = __cpu_program_ptr(); + break; + case MELF_MCPU_SEG_DATA: r = __cpu_data_alloc( n ); + *dst = __cpu_data_ptr(); + break; + case MELF_MCPU_SEG_CONTROL: r = __cpu_control_alloc( n ); + *dst = __cpu_control_ptr(); + break; + case MELF_MCPU_SEG_INPUT: r = __cpu_input_alloc( n ); + *dst = __cpu_input_ptr(); + break; + case MELF_MCPU_SEG_OUTPUT: r = __cpu_output_alloc( n ); + *dst = __cpu_output_ptr(); + break; + case MELF_MCPU_SEG_TRACE: r = __cpu_trace_alloc( n ); + *dst = __cpu_trace_ptr(); + break; + case MELF_MCPU_SEG_STACK: r = __cpu_stack_alloc( n ); + *dst = __cpu_stack_ptr(); + break; + case MELF_MCPU_SEG_HEAP: r = __cpu_heap_alloc( n, max ); + *dst = __cpu_heap_ptr(); + break; + } + return( r ); +} + +static __mpu_UNIT_t * +seg_ptr( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( __cpu_program_ptr() ); + case MELF_MCPU_SEG_DATA: return( __cpu_data_ptr() ); + case MELF_MCPU_SEG_CONTROL: return( __cpu_control_ptr() ); + case MELF_MCPU_SEG_INPUT: return( __cpu_input_ptr() ); + case MELF_MCPU_SEG_OUTPUT: return( __cpu_output_ptr() ); + case MELF_MCPU_SEG_TRACE: return( __cpu_trace_ptr() ); + default: return( 0 ); + } +} + +static int +put_startup( int argc, char **argv, __mpu_uint64_t stack_size, __mpu_uint64_t *base, char *err, size_t es ) +{ + __mpu_uint64_t count, arr, strings = 0, raw, b; + size_t i; + __mpu_UNIT_t *sp = __cpu_stack_ptr(); + if( argc < 0 || ( argc && !argv ) ) + { + snprintf( err, es, "bad argc/argv" ); + return( -1 ); + } + count = (__mpu_uint64_t)(unsigned)argc; + if( count == UINT64_MAX || count + 1 > UINT64_MAX / MCPU_STARTUP_ARGV_SIZE ) + { + snprintf( err, es, "startup argv overflow" ); + return( -1 ); + } + arr = ( count + 1 ) * MCPU_STARTUP_ARGV_SIZE; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t sl = strlen( argv[i] ); + __mpu_uint64_t z = (__mpu_uint64_t)sl + 1; + if( !z || strings > UINT64_MAX - z ) + { + snprintf( err, es, "startup strings overflow" ); + return( -1 ); + } + strings += z; + } + if( MCPU_STARTUP_SIZE > UINT64_MAX - arr || MCPU_STARTUP_SIZE + arr > UINT64_MAX - strings ) + { + snprintf( err, es, "startup size overflow" ); + return( -1 ); + } + raw = MCPU_STARTUP_SIZE + arr + strings; + if( raw > stack_size ) + { + snprintf( err, es, "command line does not fit executable STACK" ); + return( -1 ); + } + b = ( stack_size - raw ) & ~ ( (__mpu_uint64_t)MCPU_STARTUP_ALIGN - 1 ); + if( raw > stack_size - b ) + { + snprintf( err, es, "command line does not fit aligned executable STACK" ); + return( -1 ); + } + memset( sp + (size_t)b, 0, (size_t)( stack_size - b ) ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_VERSION_OFF, MCPU_STARTUP_VERSION_CURRENT ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_SIZE_OFF, MCPU_STARTUP_SIZE ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGC_OFF, count ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGV_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, b + MCPU_STARTUP_SIZE ) ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_RESERVED_OFF, 0 ); + { + __mpu_uint64_t pos = b + MCPU_STARTUP_SIZE + arr; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t z = strlen( argv[i] ) + 1; + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + i * 8 ), MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, pos ) ); + memcpy( sp + (size_t)pos, argv[i], z ); + pos += z; + } + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + (size_t)argc * 8 ), 0 ); + } + *base = b; + return( 0 ); +} + +static int +split_preloads( const char *s, char ***out, size_t *n, char *err, size_t es ) +{ + char *copy = 0, *p, *q; + char **v = 0; + size_t nv = 0, cap = 0; + *out = 0; + *n = 0; + if( !s || !*s ) return( 0 ); + copy = strdup( s ); + if( !copy ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + p = copy; + for( ; ; ) + { + q = strchr( p, ':' ); + if( q ) * q = 0; + if( !*p ) + { + snprintf( err, es, "empty path in --ldpreload list" ); + goto bad; + } + if( nv == cap ) + { + size_t nc = cap ? cap * 2 : 4; + char **z = (char **)realloc( v, nc *sizeof( *z ) ); + if( !z ) + { + snprintf( err, es, "out of memory" ); + goto bad; + } + v = z; + cap = nc; + } + v[nv] = strdup( p ); + if( !v[nv] ) + { + snprintf( err, es, "out of memory" ); + goto bad; + } + ++nv; + if( !q ) break; + p = q + 1; + } + free( copy ); + *out = v; + *n = nv; + return( 0 ); + bad: free( copy ); + while( nv ) free( v[--nv] ); + free( v ); + return( -1 ); +} + +static void +free_paths( char **v, size_t n ) +{ + while( n ) free( v[--n] ); + free( v ); +} + +static int +is_runtime_hook_name( const char *name ) +{ + if( name == NULL ) + return( 0 ); + + return( !strcmp(name, "__mcpu_libc_init") || + !strcmp(name, "__mcpu_init_array") || + !strcmp(name, "__mcpu_fini_array") || + !strcmp(name, "__mcpu_libc_fini") ); + +} /* End of is_runtime_hook_name() */ + + +static int +find_winner( winner *w, size_t nw, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < nw; ++i ) if( !strcmp( w[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +scope_class( size_t mod, size_t preload_start ) +{ + if( mod >= preload_start ) + return( 3 ); + if( mod == 0 ) + return( 2 ); + return( 1 ); + +} /* End of scope_class() */ + +static int +scoped_candidate_better( unsigned binding, size_t mod, + unsigned old_binding, size_t old_mod, + size_t preload_start ) +{ + int cls, old_cls; + + if( binding != old_binding ) + return( binding == MELF_STB_GLOBAL ); + + cls = scope_class( mod, preload_start ); + old_cls = scope_class( old_mod, preload_start ); + if( cls != old_cls ) + return( cls > old_cls ); + + if( cls == 3 ) + return( mod > old_mod ); + if( cls == 1 ) + return( mod < old_mod ); + + return( 0 ); + +} /* End of scoped_candidate_better() */ + +static int +build_winners( dmod *m, size_t nm, size_t preload_start, int scoped, + winner **wp, size_t *np, char *err, size_t es ) +{ + winner *w = 0; + size_t nw = 0, cap = 0, mi, si, x; + for( mi = 0; mi < nm; ++mi ) for( si = 1; si < m[mi].nsym; ++si ) + { + dsym *s = &m[mi].sym[si]; + if( s->binding == MELF_STB_LOCAL || s->shndx == MELF_SHN_UNDEF ) continue; + if( !strcmp( s->name, "_start" ) || !strcmp( s->name, "_life" ) || !strcmp( s->name, "_fini" ) ) continue; + + /* + * HLL runtime hooks may be supplied by the normal MCPU Libc dependency, + * but explicit preloads are never allowed to interpose them. Main- + * executable definitions still win naturally through scoped resolution. + */ + if( scoped && mi >= preload_start && is_runtime_hook_name(s->name) ) + continue; + if( !find_winner( w, nw, s->name, &x ) ) + { + winner *o = &w[x]; + int replace; + + if( scoped ) + replace = scoped_candidate_better(s->binding, mi, o->binding, o->mod, + preload_start); + else + replace = ( s->binding == MELF_STB_GLOBAL || + o->binding == MELF_STB_WEAK ); + + if( replace ) + { + o->mod = mi; + o->sym = si; + o->binding = s->binding; + } + continue; + } + if( nw == cap ) + { + size_t nc = cap ? cap * 2 : 32; + winner *q = ( winner * ) realloc( w, nc *sizeof( *q ) ); + if( !q ) + { + free( w ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + w = q; + cap = nc; + } + w[nw++] = ( winner ) + { + s->name, mi, si, s->binding + }; + } + *wp = w; + *np = nw; + return( 0 ); +} + +static int +symbol_addr( dmod *m, size_t mi, size_t si, __mpu_uint64_t *ptr, unsigned *seg, __mpu_uint64_t *off, char *err, size_t es ) +{ + dsym *s = &m[mi].sym[si]; + if( s->shndx == MELF_SHN_UNDEF || s->shndx >= m[mi].shnum ) + { + snprintf( err, es, "undefined dynamic symbol" ); + return( -1 ); + } + rsh *sh = &m[mi].sh[s->shndx]; + unsigned sg = MELF_MCPU_PTR64_SEG( sh->addr ); + __mpu_uint64_t rel = MELF_MCPU_PTR64_OFFSET( sh->addr ), o; + if( addu64( m[mi].base[sg], rel, &o ) || addu64( o, s->value, &o ) || o > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( ptr, sg, o ) ) + { + snprintf( err, es, "dynamic symbol address overflow: %s", s->name ); + return( -1 ); + } + *seg = sg; + *off = o; + return( 0 ); +} + +static int +cpu_dyn_load_internal( const char *path, const char *preload_list, + int allow_needed, size_t preload_count, + int scoped_resolution, int argc, char **argv, + cpu_exec_loaded *ld, char *err, size_t es ) +{ + char **paths = 0; + size_t np = 0, nm = 0, preload_start = 0, i, j; + dmod *mods = 0; + winner *w = 0; + size_t nw = 0; + __mpu_uint64_t cursor[8] = + { + 0 + } + , segsize[8] = + { + 0 + }; + int rc = -1; + __mpu_UNIT_t *dst; + memset( ld, 0, sizeof( *ld ) ); + if( split_preloads( preload_list, &paths, &np, err, es ) ) return( -1 ); + if( scoped_resolution && preload_count > np ) + { + snprintf( err, es, "invalid resolved preload count" ); + free_paths( paths, np ); + return( -1 ); + } + nm = np + 1; + preload_start = scoped_resolution ? nm - preload_count : nm; + mods = ( dmod * ) calloc( nm, sizeof( *mods ) ); + if( !mods ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + if( parse_module( path, 1, allow_needed ? 0 : 1, &mods[0], err, es ) ) + goto out; + for( i = 0; i < np; ++i ) + if( parse_module(paths[i], 0, allow_needed ? 0 : 1, + &mods[i + 1], err, es) ) + goto out; + /* process layout: main bases are zero; shared objects append per segment */ + for( i = 0; i < 8; ++i ) + { + if( mods[0].load_index[i] >= 0 ) cursor[i] = mods[0].ph[mods[0].load_index[i]].memsz; + } + for( i = 1; i < nm; ++i ) for( j = 0; j < 8; ++j ) if( mods[i].load_index[j] >= 0 ) + { + rph *p = &mods[i].ph[mods[i].load_index[j]]; + int ov = 0; + if( j == MELF_MCPU_SEG_DATA && cursor[j] == 0 ) cursor[j] = CPU_DATA_NULL_GUARD_SIZE; + cursor[j] = cpu_melf_align_up( cursor[j], nzalign( p->align ), &ov ); + if( ov || cursor[j] > CPU_PTR_OFFSET_MASK - p->memsz ) + { + snprintf( err, es, "dynamic segment layout overflow" ); + goto out; + } + mods[i].base[j] = cursor[j]; + cursor[j] += p->memsz; + } + for( i = 0; i < 8; ++i ) segsize[i] = cursor[i]; + /* allocate/copy image-backed process segments */ + for( i = 0; i <= MELF_MCPU_SEG_TRACE; ++i ) if( segsize[i] ) + { + if( seg_alloc( (unsigned)i, segsize[i], segsize[i], &dst ) ) + { + snprintf( err, es, "cannot allocate process segment %zu", i ); + goto out; + } + memset( dst, 0, (size_t)segsize[i] ); + } + for( i = 0; i < nm; ++i ) for( j = 0; j < 8; ++j ) if( mods[i].load_index[j] >= 0 ) + { + rph *p = &mods[i].ph[mods[i].load_index[j]]; + dst = seg_ptr( (unsigned)j ); + if( p->filesz ) memcpy( dst + (size_t)mods[i].base[j], mods[i].file + (size_t)p->off, (size_t)p->filesz ); + } + if( build_winners(mods, nm, preload_start, scoped_resolution, + &w, &nw, err, es) ) goto out; + /* apply every retained startup relocation */ + for( i = 0; i < nm; ++i ) for( j = 0; j < mods[i].nrela; ++j ) + { + drela *r = &mods[i].rela[j]; + dsym *ref = &mods[i].sym[r->sym]; + size_t wi = 0; + __mpu_uint64_t tptr = 0, toff = 0, final = 0, field; + unsigned tseg = 0, sseg; + int unresolved = 0; + int local = ( ref->binding == MELF_STB_LOCAL ); + rsh *ssh = &mods[i].sh[r->target_shndx]; + sseg = MELF_MCPU_PTR64_SEG( ssh->addr ); + if( local ) + { + if( ref->shndx == MELF_SHN_UNDEF ) + { + snprintf( err, es, "undefined LOCAL dynamic symbol" ); + goto out; + } + if( symbol_addr( mods, i, r->sym, &tptr, &tseg, &toff, err, es ) ) goto out; + } + else unresolved = find_winner( w, nw, ref->name, &wi ); + if( addu64( mods[i].base[sseg], MELF_MCPU_PTR64_OFFSET( ssh->addr ), &field ) || addu64( field, r->offset, &field ) || field > segsize[sseg] || 8 > segsize[sseg] - field ) + { + snprintf( err, es, "runtime relocation field outside process image" ); + goto out; + } + if( unresolved ) + { + if( ref->binding == MELF_STB_WEAK && ( r->type == CPU_RELOC_PTR64_CODE || r->type == CPU_RELOC_PTR64_DATA || r->type == CPU_RELOC_CPTR64_DATA ) && r->addend == 0 ) + { + final = 0; + } + else + { + snprintf( err, es, "unresolved dynamic symbol: %s", ref->name ); + goto out; + } + } + else + { + if( !local && symbol_addr( mods, w[wi].mod, w[wi].sym, &tptr, &tseg, &toff, err, es ) ) goto out; + if( addsigned( toff, r->addend, &final ) ) + { + snprintf( err, es, "dynamic relocation overflow: %s", ref->name ); + goto out; + } + } + unsigned char *fp = seg_ptr( sseg ) + (size_t)field; + switch( r->type ) + { + case CPU_RELOC_CODE64: if( unresolved || tseg != MELF_MCPU_SEG_PROGRAM || final >= segsize[MELF_MCPU_SEG_PROGRAM] ) + { + snprintf( err, es, "CODE64 dynamic target is not PROGRAM: %s", ref->name ); + goto out; + } + __cpu_code_put_u64( fp, final ); + break; + case CPU_RELOC_DATA64: if( unresolved || tseg != MELF_MCPU_SEG_DATA || final > segsize[MELF_MCPU_SEG_DATA] ) + { + snprintf( err, es, "DATA64 dynamic target is not DATA: %s", ref->name ); + goto out; + } + __cpu_code_put_u64( fp, final ); + break; + case CPU_RELOC_PTR64_CODE: if( unresolved ) __cpu_code_put_u64( fp, 0 ); + else + { + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 dynamic target overflow" ); + goto out; + } + __cpu_code_put_u64( fp, tptr ); + } break; + case CPU_RELOC_PTR64_DATA: if( unresolved ) __cpu_data_put_u64( fp, 0 ); + else + { + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 dynamic target overflow" ); + goto out; + } + __cpu_data_put_u64( fp, tptr ); + } break; + case CPU_RELOC_CPTR64_DATA: if( unresolved ) __cpu_data_put_u64( fp, 0 ); + else + { + if( tseg != MELF_MCPU_SEG_PROGRAM || final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, MELF_MCPU_SEG_PROGRAM, final ) ) + { + snprintf( err, es, "CPTR64 dynamic target is not PROGRAM" ); + goto out; + } + __cpu_data_put_u64( fp, tptr ); + } break; + default: snprintf( err, es, "unsupported runtime relocation" ); + goto out; + } + } + /* instantiate process-owned STACK/HEAP only after all startup linking succeeds */ + { + rph *sp = &mods[0].ph[mods[0].stacki]; + if( seg_alloc( MELF_MCPU_SEG_STACK, sp->memsz, sp->memsz, &dst ) ) + { + snprintf( err, es, "cannot allocate STACK" ); + goto out; + } + ld->stack_size = sp->memsz; + if( mods[0].heapi >= 0 ) + { + rph *hp = &mods[0].ph[mods[0].heapi]; + if( seg_alloc( MELF_MCPU_SEG_HEAP, hp->memsz, hp->maxsz, &dst ) ) + { + snprintf( err, es, "cannot allocate HEAP" ); + goto out; + } + } + } + if( put_startup( argc, argv, ld->stack_size, &ld->startup_base, err, es ) ) + goto out; + if( __cpu_program_protect() ) + { + snprintf( err, es, "cannot protect PROGRAM" ); + goto out; + } + ld->start_entry = MELF_MCPU_PTR64_OFFSET( mods[0].entry ); + ld->life_entry = MELF_MCPU_PTR64_OFFSET( mods[0].life ); + ld->has_init = mods[0].init != 0; + ld->init_entry = ld->has_init ? MELF_MCPU_PTR64_OFFSET( mods[0].init ) : 0; + ld->has_fini = mods[0].fini != 0; + ld->fini_entry = ld->has_fini ? MELF_MCPU_PTR64_OFFSET( mods[0].fini ) : 0; + ld->has_data_section = mods[0].has_data_section; + ld->data_section_begin = mods[0].data_section_begin; + ld->data_section_size = mods[0].data_section_size; + __cpu_pc = ld->start_entry; + __cpu_sp = ld->startup_base; + __cpu_fp = ld->stack_size; + rc = 0; +out: free( w ); +if( mods ) +{ + for( i = 0; i < nm; ++i ) free_mod( &mods[i] ); + free( mods ); +} +free_paths( paths, np ); +return( rc ); +} + + +int +cpu_dyn_load( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + return( cpu_dyn_load_internal(path, preload_list, 0, 0, 0, + argc, argv, ld, err, es) ); + +} /* End of cpu_dyn_load() */ + + +int +cpu_dyn_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + return( cpu_dyn_load_internal(path, module_list, 1, preload_count, 1, + argc, argv, ld, err, es) ); + +} /* End of cpu_dyn_load_resolved() */ diff --git a/utils/mcpu-dyn.h b/utils/mcpu-dyn.h new file mode 100644 index 0000000..1fc0aa2 --- /dev/null +++ b/utils/mcpu-dyn.h @@ -0,0 +1,66 @@ +#ifndef __CPU_DYN_H +#define __CPU_DYN_H + +#include "mcpu-asm.h" +#include "mcpu-exec.h" + + +typedef struct cpu_dyn_library_symbol +{ + char *name; + unsigned binding; + unsigned type; +} cpu_dyn_library_symbol; + +typedef struct cpu_dyn_library_info +{ + char *soname; + cpu_dyn_library_symbol *symbols; + size_t symbol_count; +} cpu_dyn_library_info; + +typedef struct cpu_dyn_metadata +{ + const char *soname; + const char *const *needed; + size_t needed_count; +} cpu_dyn_metadata; + +typedef struct cpu_dyn_dependency_info +{ + int abi; + char *soname; + char **needed; + size_t needed_count; +} cpu_dyn_dependency_info; + +int cpu_dyn_write( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, int shared, + char *err, size_t errsz); + +int cpu_dyn_write_abi3( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, int shared, + const cpu_dyn_metadata *metadata, + char *err, size_t errsz); + +int cpu_dyn_library_read( const char *path, cpu_dyn_library_info *info, + char *err, size_t errsz ); +void cpu_dyn_library_free( cpu_dyn_library_info *info ); +int cpu_dyn_library_find_symbol( const cpu_dyn_library_info *info, + const char *name, + const cpu_dyn_library_symbol **symbol ); + +int cpu_dyn_dependency_read( const char *path, int want_exec, + cpu_dyn_dependency_info *info, + char *err, size_t errsz ); +void cpu_dyn_dependency_free( cpu_dyn_dependency_info *info ); + +int cpu_dyn_load( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_dyn_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-exec.c b/utils/mcpu-exec.c new file mode 100644 index 0000000..09b5256 --- /dev/null +++ b/utils/mcpu-exec.c @@ -0,0 +1,1483 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-exec.h" +#include "mcpu-melf.h" +#include "mcpu-dyn.h" + +typedef struct blob +{ + unsigned char *p; + size_t n; + size_t cap; +} blob; + +typedef struct xsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint64_t align; + __mpu_uint64_t entsize; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint32_t nameoff; +} xsec; + +typedef struct xph +{ + __mpu_uint32_t type; + __mpu_uint32_t flags; + __mpu_uint64_t off; + __mpu_uint64_t vaddr; + __mpu_uint64_t paddr; + __mpu_uint64_t filesz; + __mpu_uint64_t memsz; + __mpu_uint64_t maxsz; + __mpu_uint64_t align; +} xph; + +typedef struct rsh +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} rsh; + +typedef xph rph; + +typedef struct exec_section_ranges +{ + int has_init; + int has_text; + int has_fini; + __mpu_uint64_t init_begin, init_end; + __mpu_uint64_t text_begin, text_end; + __mpu_uint64_t fini_begin, fini_end; +} exec_section_ranges; + +static int +grow( blob *b, size_t add ) +{ + size_t need, c; + unsigned char *q; + + if( add > SIZE_MAX - b->n ) + return( -1 ); + need = b->n + add; + c = b->cap ? b->cap : 64; + while( c < need ) + { + if( c > SIZE_MAX / 2 ) + return( -1 ); + c *= 2; + } + if( c == b->cap ) + return( 0 ); + q = (unsigned char *)realloc( b->p, c ); + if( !q ) + return( -1 ); + b->p = q; + b->cap = c; + return( 0 ); +} + +static int +bput( blob *b, const void *p, size_t n ) +{ + if( grow( b, n ) ) + return( -1 ); + if( n ) + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +bstr( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + if( b->n > UINT32_MAX || n > UINT32_MAX - b->n ) + return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( bput( b, s, n ) ); +} + +static __mpu_uint64_t +maxu( __mpu_uint64_t a, __mpu_uint64_t b ) +{ + return( a > b ? a : b ); +} + +static __mpu_uint64_t +nzalign( __mpu_uint64_t a ) +{ + return( a ? a : 1 ); +} + +static int +add_u64( __mpu_uint64_t a, __mpu_uint64_t b, __mpu_uint64_t *r ) +{ + if( a > UINT64_MAX - b ) + return( -1 ); + *r = a + b; + return( 0 ); +} + +static void +put_ehdr( unsigned char *p, int data, const cpu_asm_image *im, + __mpu_uint64_t phoff, __mpu_uint16_t phnum, + __mpu_uint64_t shoff, __mpu_uint16_t shnum, + __mpu_uint16_t shstr) +{ + memset( p, 0, MELF64_EHDR_SIZE ); + p[MELF_EI_MAG0] = MELFMAG0; + p[MELF_EI_MAG1] = MELFMAG1; + p[MELF_EI_MAG2] = MELFMAG2; + p[MELF_EI_MAG3] = MELFMAG3; + p[MELF_EI_CLASS] = MELF_CLASS_64; + p[MELF_EI_DATA] = (unsigned char)data; + p[MELF_EI_VERSION] = MELF_EV_CURRENT; + p[MELF_EI_OSABI] = MELF_OSABI_MCPU; + p[MELF_EI_ABIVERSION] = MELF_ABIVERSION_CURRENT; + + cpu_melf_put16( p + MELF64_EHDR_E_TYPE_OFF, MELF_ET_EXEC, data ); + cpu_melf_put16( p + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( p + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + cpu_melf_put64( p + MELF64_EHDR_E_ENTRY_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->start_entry ), data); + cpu_melf_put64( p + MELF64_EHDR_E_INIT_ENTRY_OFF, + im->has_init ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_entry ) : 0, + data); + cpu_melf_put64( p + MELF64_EHDR_E_LIFE_ENTRY_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->life_entry ), data); + cpu_melf_put64( p + MELF64_EHDR_E_FINI_ENTRY_OFF, + im->has_fini ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_entry ) : 0, + data); + cpu_melf_put64( p + MELF64_EHDR_E_PHOFF_OFF, phoff, data ); + cpu_melf_put64( p + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( p + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHENTSIZE_OFF, MELF64_PHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHNUM_OFF, phnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHNUM_OFF, shnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHSTRNDX_OFF, shstr, data ); +} + +static void +put_phdr( unsigned char *p, const xph *x, int data ) +{ + memset( p, 0, MELF64_PHDR_SIZE ); + cpu_melf_put32( p + MELF64_PHDR_P_TYPE_OFF, x->type, data ); + cpu_melf_put32( p + MELF64_PHDR_P_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_PHDR_P_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_PHDR_P_VADDR_OFF, x->vaddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_PADDR_OFF, x->paddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_FILESZ_OFF, x->filesz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MEMSZ_OFF, x->memsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MAXSZ_OFF, x->maxsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_ALIGN_OFF, x->align, data ); +} + +static void +put_shdr( unsigned char *p, const xsec *x, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, x->nameoff, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, x->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, x->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, x->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, x->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, x->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, x->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, x->entsize, data ); +} + +static void +get_phdr( const unsigned char *p, int data, rph *x ) +{ + x->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + x->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + x->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + x->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + x->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + x->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + x->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); +} + +static void +get_shdr( const unsigned char *p, int data, rsh *x ) +{ + x->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + x->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + x->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + x->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + x->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + x->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + x->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + x->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +add_load( xph *ph, size_t *np, __mpu_uint64_t *cur, + unsigned seg, __mpu_uint64_t filesz, __mpu_uint64_t memsz, + __mpu_uint32_t flags, __mpu_uint64_t align, + char *err, size_t es) +{ + int ov = 0; + + align = nzalign( align ); + if( !cpu_melf_is_pow2( align ) ) + { + snprintf( err, es, "invalid load alignment" ); + return( -1 ); + } + if( filesz > memsz || memsz > CPU_PTR_OFFSET_MASK || filesz > SIZE_MAX ) + { + snprintf( err, es, "invalid load size" ); + return( -1 ); + } + + *cur = cpu_melf_align_up( *cur, align, &ov ); + if( ov ) + { + snprintf( err, es, "executable layout overflow" ); + return( -1 ); + } + + ph[*np] = ( xph ) + { + MELF_PT_LOAD, flags, *cur, MELF_MCPU_PTR64( seg, 0 ), 0, + filesz, memsz, memsz, align + }; + ++*np; + + if( add_u64( *cur, filesz, cur ) ) + { + snprintf( err, es, "executable layout overflow" ); + return( -1 ); + } + return( 0 ); +} + +static int +image_range( __mpu_uint64_t off, __mpu_uint64_t size, __mpu_uint64_t limit ) +{ + return( off <= limit && size <= limit - off ); +} + +int +cpu_exec_write( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, char *err, size_t es) +{ + xph ph[8]; + size_t np = 0; + xsec s[20]; + size_t ns = 1; + blob shstr = {0}; + int data = cpu_melf_data_encoding(), ov = 0; + __mpu_uint64_t phoff = MELF64_EHDR_SIZE, cur, shoff, total; + __mpu_uint64_t data_mem_size; + unsigned char *file = 0; + unsigned char tbuf[MELF_MCPU_TARGET_SIZE], lbuf[MELF_MCPU_LIFECYCLE_SIZE]; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + __mpu_uint16_t shstridx, targetidx, lifeidx; + size_t i; + int phidx[8] = + { + -1, -1, -1, -1, -1, -1, -1, -1 + }; + int have_data; + __mpu_uint64_t palign; + + if( !path || !im || !op || !im->has_start || !im->has_life || !im->code_size ) + { + snprintf( err, es, "incomplete executable image" ); + return( -1 ); + } + if( im->start_entry >= im->code_size || im->life_entry >= im->code_size || + (im->has_init && im->init_entry >= im->code_size) || + (im->has_fini && im->fini_entry >= im->code_size)) + { + snprintf( err, es, "lifecycle entry is outside PROGRAM" ); + return( -1 ); + } + if( im->has_init && im->start_entry != im->init_entry ) + { + snprintf( err, es, "INIT entry must equal _start" ); + return( -1 ); + } + if( !im->has_init && im->start_entry != im->life_entry ) + { + snprintf( err, es, "without INIT, _start must equal _life" ); + return( -1 ); + } + if( !im->text_present || + !image_range( im->text_offset, im->text_size, im->code_size ) || + im->life_entry < im->text_offset || + im->life_entry - im->text_offset >= im->text_size) + { + snprintf( err, es, "_life entry must be inside .text" ); + return( -1 ); + } + if( im->has_init && + (!im->init_present || + !image_range( im->init_offset, im->init_size, im->code_size ) || + im->start_entry < im->init_offset || + im->start_entry - im->init_offset >= im->init_size)) + { + snprintf( err, es, "_start entry must be inside .init" ); + return( -1 ); + } + if( im->has_fini && + (!im->fini_present || + !image_range( im->fini_offset, im->fini_size, im->code_size ) || + im->fini_entry < im->fini_offset || + im->fini_entry - im->fini_offset >= im->fini_size)) + { + snprintf( err, es, "_fini entry must be inside .fini" ); + return( -1 ); + } + if( !op->stack_size || op->stack_size % MCPU_STARTUP_ALIGN || + op->stack_size > CPU_PTR_OFFSET_MASK || op->stack_size > SIZE_MAX) + { + snprintf( err, es, "STACK size must be a positive 16-byte-aligned PTR64 offset" ); + return( -1 ); + } + if( op->have_heap && + (op->heap_size < CPU_HEAP_BEGIN || op->heap_limit < op->heap_size || + op->heap_limit > CPU_PTR_OFFSET_MASK || op->heap_size > SIZE_MAX)) + { + snprintf( err, es, "invalid HEAP size/limit" ); + return( -1 ); + } + + data_mem_size = im->data_mem_size ? im->data_mem_size : im->data_size; + if( data_mem_size < im->data_size ) + { + snprintf( err, es, "DATA memory size is smaller than DATA file image" ); + return( -1 ); + } + have_data = data_mem_size || im->data_present || im->bss_present; + if( have_data && ( data_mem_size < CPU_DATA_NULL_GUARD_SIZE || + im->data_size < CPU_DATA_NULL_GUARD_SIZE)) + { + snprintf( err, es, "DATA image lacks the mandatory 16-byte NULL guard" ); + return( -1 ); + } + + palign = maxu( maxu( nzalign( im->init_align ), nzalign( im->text_align ) ), + nzalign( im->fini_align )); + + /* Count PHDRs first because file payload begins after the whole table. */ + np = 1 + (size_t)have_data + + (im->control_size || im->control_present) + + (im->input_size || im->input_present) + + (im->output_size || im->output_present) + + (im->trace_size || im->trace_present) + + 1 + (op->have_heap ? 1 : 0); + if( np > 8 || phoff > UINT64_MAX - np *MELF64_PHDR_SIZE ) + { + snprintf( err, es, "too many program headers" ); + return( -1 ); + } + cur = phoff + np * MELF64_PHDR_SIZE; + np = 0; + + phidx[MELF_MCPU_SEG_PROGRAM] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_PROGRAM, + im->code_size, im->code_size, MELF_PFLAGS_PROGRAM, + palign, err, es)) + goto fail; + + if( have_data ) + { + phidx[MELF_MCPU_SEG_DATA] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_DATA, + im->data_size, data_mem_size, MELF_PFLAGS_DATA, + maxu( 16, maxu( nzalign( im->data_align ), nzalign( im->bss_align ) ) ), + err, es)) + goto fail; + } + if( im->control_size || im->control_present ) + { + phidx[MELF_MCPU_SEG_CONTROL] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_CONTROL, + im->control_size, im->control_size, MELF_PFLAGS_CONTROL, + nzalign( im->control_align ), err, es)) + goto fail; + } + if( im->input_size || im->input_present ) + { + phidx[MELF_MCPU_SEG_INPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_INPUT, + im->input_size, im->input_size, MELF_PFLAGS_INPUT, + nzalign( im->input_align ), err, es)) + goto fail; + } + if( im->output_size || im->output_present ) + { + phidx[MELF_MCPU_SEG_OUTPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_OUTPUT, + im->output_size, im->output_size, MELF_PFLAGS_OUTPUT, + nzalign( im->output_align ), err, es)) + goto fail; + } + if( im->trace_size || im->trace_present ) + { + phidx[MELF_MCPU_SEG_TRACE] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_TRACE, + im->trace_size, im->trace_size, MELF_PFLAGS_TRACE, + nzalign( im->trace_align ), err, es)) + goto fail; + } + + phidx[MELF_MCPU_SEG_STACK] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_STACK, MELF_PFLAGS_STACK, 0, MELF_MCPU_VADDR_STACK, 0, + 0, op->stack_size, op->stack_size, MCPU_STARTUP_ALIGN + }; + if( op->have_heap ) + { + phidx[MELF_MCPU_SEG_HEAP] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_HEAP, MELF_PFLAGS_HEAP, 0, MELF_MCPU_VADDR_HEAP, 0, + 0, op->heap_size, op->heap_limit, 16 + }; + } + + memset( s, 0, sizeof( s ) ); +#define ADDSEC(N,T,F,A,O,Z,AL,E) do { \ + if(ns >= sizeof(s)/sizeof(s[0])) { snprintf(err,es,"too many sections"); goto fail; } \ + s[ns] = (xsec){(N),(T),(F),(A),(O),(Z),(AL),(E),0,0,0}; \ + ++ns; \ + } while(0) + + if( im->init_present ) + { + if( !image_range( im->init_offset, im->init_size, im->code_size ) ) + { + snprintf( err, es, ".init range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_INIT, MELF_SHT_PROGBITS, MELF_SHFLAGS_INIT, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->init_offset, + im->init_size, nzalign( im->init_align ), 0); + } + + if( im->text_present ) + { + if( !image_range( im->text_offset, im->text_size, im->code_size ) ) + { + snprintf( err, es, ".text range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_TEXT, MELF_SHT_PROGBITS, MELF_SHFLAGS_TEXT, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->text_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->text_offset, + im->text_size, nzalign( im->text_align ), 0); + } + + if( im->fini_present ) + { + if( !image_range( im->fini_offset, im->fini_size, im->code_size ) ) + { + snprintf( err, es, ".fini range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_FINI, MELF_SHT_PROGBITS, MELF_SHFLAGS_FINI, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->fini_offset, + im->fini_size, nzalign( im->fini_align ), 0); + } + + if( im->data_present ) + { + if( phidx[MELF_MCPU_SEG_DATA] < 0 || + !image_range( im->data_offset, im->data_section_size, im->data_size )) + { + snprintf( err, es, ".data range outside DATA file image" ); + goto fail; + } + ADDSEC( MELF_SEC_DATA, MELF_SHT_PROGBITS, MELF_SHFLAGS_DATA, + MELF_MCPU_PTR64( MELF_MCPU_SEG_DATA, im->data_offset ), + ph[phidx[MELF_MCPU_SEG_DATA]].off + im->data_offset, + im->data_section_size, nzalign( im->data_align ), 0); + } + + if( im->bss_present ) + { + if( phidx[MELF_MCPU_SEG_DATA] < 0 || + !image_range( im->bss_offset, im->bss_size, data_mem_size )) + { + snprintf( err, es, ".bss range outside DATA memory image" ); + goto fail; + } + ADDSEC( MELF_SEC_BSS, MELF_SHT_NOBITS, MELF_SHFLAGS_BSS, + MELF_MCPU_PTR64( MELF_MCPU_SEG_DATA, im->bss_offset ), + ph[phidx[MELF_MCPU_SEG_DATA]].off + im->bss_offset, + im->bss_size, nzalign( im->bss_align ), 0); + } + +#define ADD_DATA_LIKE(PRESENT,SEG,NAME,FLAGS,OFFSET,SIZE,ALIGN) do { \ + if((PRESENT)) { \ + if(phidx[(SEG)] < 0 || !image_range((OFFSET),(SIZE),ph[phidx[(SEG)]].memsz)) { \ + snprintf(err,es,"%s range outside segment",(NAME)); goto fail; \ + } \ + ADDSEC((NAME),MELF_SHT_PROGBITS,(FLAGS),MELF_MCPU_PTR64((SEG),(OFFSET)), \ + ph[phidx[(SEG)]].off+(OFFSET),(SIZE),nzalign((ALIGN)),0); \ + } \ + } while(0) + ADD_DATA_LIKE( im->control_present, MELF_MCPU_SEG_CONTROL, MELF_SEC_CONTROL, + MELF_SHFLAGS_CONTROL, im->control_offset, im->control_section_size, + im->control_align); + ADD_DATA_LIKE( im->input_present, MELF_MCPU_SEG_INPUT, MELF_SEC_INPUT, + MELF_SHFLAGS_INPUT, im->input_offset, im->input_section_size, + im->input_align); + ADD_DATA_LIKE( im->output_present, MELF_MCPU_SEG_OUTPUT, MELF_SEC_OUTPUT, + MELF_SHFLAGS_OUTPUT, im->output_offset, im->output_section_size, + im->output_align); + ADD_DATA_LIKE( im->trace_present, MELF_MCPU_SEG_TRACE, MELF_SEC_TRACE, + MELF_SHFLAGS_TRACE, im->trace_offset, im->trace_section_size, + im->trace_align); +#undef ADD_DATA_LIKE + + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( tbuf, &mt, data ); + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov ) goto overflow; + targetidx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_MCPU_TARGET, MELF_SHT_MCPU_TARGET, 0, 0, cur, + MELF_MCPU_TARGET_SIZE, 8, MELF_MCPU_TARGET_SIZE); + if( add_u64( cur, MELF_MCPU_TARGET_SIZE, &cur ) ) goto overflow; + + cpu_melf_fill_lifecycle( &ml ); + cpu_melf_encode_lifecycle( lbuf, &ml, data ); + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov ) goto overflow; + lifeidx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_MCPU_LIFECYCLE, MELF_SHT_MCPU_LIFECYCLE, 0, 0, cur, + MELF_MCPU_LIFECYCLE_SIZE, 8, MELF_MCPU_LIFECYCLE_SIZE); + if( add_u64( cur, MELF_MCPU_LIFECYCLE_SIZE, &cur ) ) goto overflow; + + if( bput( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + shstridx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_SHSTRTAB, MELF_SHT_STRTAB, 0, 0, 0, 0, 1, 0 ); + for( i = 1; i < ns; ++i ) + if( bstr( &shstr, s[i].name, &s[i].nameoff ) ) + { + snprintf( err, es, "section string table too large" ); + goto fail; + } + + s[shstridx].off = cur; + s[shstridx].size = shstr.n; + if( add_u64( cur, shstr.n, &cur ) ) goto overflow; +#undef ADDSEC + + shoff = cpu_melf_align_up( cur, 8, &ov ); + if( ov || ns > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - ns * MELF64_SHDR_SIZE) + goto overflow; + total = shoff + ns * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, es, "executable too large for this host" ); + goto fail; + } + + file = (unsigned char *)calloc( (size_t)total, 1 ); + if( !file ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + put_ehdr( file, data, im, phoff, (__mpu_uint16_t)np, + shoff, (__mpu_uint16_t)ns, shstridx); + for( i = 0; i < np; ++i ) + put_phdr( file + (size_t)phoff + i *MELF64_PHDR_SIZE, &ph[i], data ); + + for( i = 0; i < np; ++i ) + if( ph[i].type == MELF_PT_LOAD && ph[i].filesz ) + { + const unsigned char *payload = 0; + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: payload = im->code; + break; + case MELF_MCPU_SEG_DATA: payload = im->data; + break; + case MELF_MCPU_SEG_CONTROL: payload = im->control; + break; + case MELF_MCPU_SEG_INPUT: payload = im->input; + break; + case MELF_MCPU_SEG_OUTPUT: payload = im->output; + break; + case MELF_MCPU_SEG_TRACE: payload = im->trace; + break; + default: break; + } + if( !payload ) + { + snprintf( err, es, "missing PT_LOAD payload" ); + goto fail; + } + memcpy( file + (size_t)ph[i].off, payload, (size_t)ph[i].filesz ); + } + + memcpy( file + (size_t)s[targetidx].off, tbuf, MELF_MCPU_TARGET_SIZE ); + memcpy( file + (size_t)s[lifeidx].off, lbuf, MELF_MCPU_LIFECYCLE_SIZE ); + memcpy( file + (size_t)s[shstridx].off, shstr.p, shstr.n ); + for( i = 0; i < ns; ++i ) + put_shdr( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &s[i], data ); + + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) + goto fail; + + free( file ); + free( shstr.p ); + return( 0 ); + +overflow: + snprintf( err, es, "executable layout overflow" ); +fail: + free( file ); + free( shstr.p ); + return( -1 ); +} + +static int +validate_ident( const unsigned char *f, size_t fn, int *data, + char *err, size_t es) +{ + if( fn < MELF64_EHDR_SIZE || + f[MELF_EI_MAG0] != MELFMAG0 || f[MELF_EI_MAG1] != MELFMAG1 || + f[MELF_EI_MAG2] != MELFMAG2 || f[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "not a MELF executable" ); + return( -1 ); + } + if( f[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, es, "unsupported MELF class" ); + return( -1 ); + } + *data = f[MELF_EI_DATA]; + if( *data != MELF_DATA_LSB && *data != MELF_DATA_MSB ) + { + snprintf( err, es, "unsupported EI_DATA" ); + return( -1 ); + } + if( f[MELF_EI_VERSION] != MELF_EV_CURRENT || + f[MELF_EI_OSABI] != MELF_OSABI_MCPU || + f[MELF_EI_ABIVERSION] != MELF_ABIVERSION_CURRENT || + f[9] || f[10] || f[11] || f[12] || f[13] || f[14] || f[15] || + cpu_melf_get16( f + MELF64_EHDR_E_TYPE_OFF, *data ) != MELF_ET_EXEC || + cpu_melf_get16( f + MELF64_EHDR_E_MACHINE_OFF, *data ) != MELF_EM_MCPU || + cpu_melf_get32( f + MELF64_EHDR_E_VERSION_OFF, *data ) != MELF_EV_CURRENT || + cpu_melf_get32( f + MELF64_EHDR_E_FLAGS_OFF, *data ) != MELF_EF_MCPU_NONE || + cpu_melf_get16( f + MELF64_EHDR_E_EHSIZE_OFF, *data ) != MELF64_EHDR_SIZE || + cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, *data ) != MELF64_PHDR_SIZE || + cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, *data ) != MELF64_SHDR_SIZE) + { + snprintf( err, es, "unsupported MELF executable header" ); + return( -1 ); + } + return( 0 ); +} + +static int +seg_alloc( unsigned seg, __mpu_uint64_t n, __mpu_uint64_t max, __mpu_UNIT_t **dst ) +{ + int r = -1; + *dst = 0; + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: r = __cpu_program_alloc( n ); + *dst = __cpu_program_ptr(); + break; + case MELF_MCPU_SEG_DATA: r = __cpu_data_alloc( n ); + *dst = __cpu_data_ptr(); + break; + case MELF_MCPU_SEG_CONTROL: r = __cpu_control_alloc( n ); + *dst = __cpu_control_ptr(); + break; + case MELF_MCPU_SEG_INPUT: r = __cpu_input_alloc( n ); + *dst = __cpu_input_ptr(); + break; + case MELF_MCPU_SEG_OUTPUT: r = __cpu_output_alloc( n ); + *dst = __cpu_output_ptr(); + break; + case MELF_MCPU_SEG_TRACE: r = __cpu_trace_alloc( n ); + *dst = __cpu_trace_ptr(); + break; + case MELF_MCPU_SEG_STACK: r = __cpu_stack_alloc( n ); + *dst = __cpu_stack_ptr(); + break; + case MELF_MCPU_SEG_HEAP: r = __cpu_heap_alloc( n, max ); + *dst = __cpu_heap_ptr(); + break; + default: break; + } + return( r ); +} + +static int +expected_pflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_PFLAGS_PROGRAM ); + case MELF_MCPU_SEG_DATA: return( MELF_PFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_PFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_PFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_PFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_PFLAGS_TRACE ); + case MELF_MCPU_SEG_STACK: return( MELF_PFLAGS_STACK ); + case MELF_MCPU_SEG_HEAP: return( MELF_PFLAGS_HEAP ); + default: return( -1 ); + } +} + +static __mpu_uint64_t +expected_shflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_SHFLAGS_TEXT ); + case MELF_MCPU_SEG_DATA: return( MELF_SHFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_SHFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static int +image_backed_seg( unsigned seg ) +{ + return seg == MELF_MCPU_SEG_PROGRAM || seg == MELF_MCPU_SEG_DATA || + seg == MELF_MCPU_SEG_CONTROL || seg == MELF_MCPU_SEG_INPUT || + seg == MELF_MCPU_SEG_OUTPUT || seg == MELF_MCPU_SEG_TRACE; +} + +static int +name_at( const unsigned char *tab, size_t n, __mpu_uint32_t off, const char **name ) +{ + if( off >= n ) + return( -1 ); + *name = (const char *)tab + off; + return( memchr( *name, 0, n - off ) ? 0 : -1 ); +} + +static int +range_contains( __mpu_uint64_t begin, __mpu_uint64_t end, __mpu_uint64_t value ) +{ + return( value >= begin && value < end ); +} + +static int +record_lifecycle_section( exec_section_ranges *r, const char *name, + __mpu_uint64_t begin, __mpu_uint64_t size, + char *err, size_t es) +{ + __mpu_uint64_t end; + if( add_u64( begin, size, &end ) ) + { + snprintf( err, es, "section address overflow" ); + return( -1 ); + } + if( !strcmp( name, MELF_SEC_INIT ) ) + { + if( r->has_init++ ) + { + snprintf( err, es, "duplicate .init section" ); + return( -1 ); + } + r->init_begin = begin; + r->init_end = end; + } + else if( !strcmp( name, MELF_SEC_TEXT ) ) + { + if( r->has_text++ ) + { + snprintf( err, es, "duplicate .text section" ); + return( -1 ); + } + r->text_begin = begin; + r->text_end = end; + } + else if( !strcmp( name, MELF_SEC_FINI ) ) + { + if( r->has_fini++ ) + { + snprintf( err, es, "duplicate .fini section" ); + return( -1 ); + } + r->fini_begin = begin; + r->fini_end = end; + } + return( 0 ); +} + +static int +put_startup( int argc, char **argv, __mpu_uint64_t stack_size, + __mpu_uint64_t *base, char *err, size_t es) +{ + __mpu_uint64_t count, arr, strings = 0, raw, b; + size_t i; + __mpu_UNIT_t *sp = __cpu_stack_ptr(); + + if( argc < 0 || ( argc && !argv ) ) + { + snprintf( err, es, "bad argc/argv" ); + return( -1 ); + } + count = (__mpu_uint64_t)(unsigned)argc; + if( count == UINT64_MAX || count + 1 > UINT64_MAX / MCPU_STARTUP_ARGV_SIZE ) + { + snprintf( err, es, "startup argv overflow" ); + return( -1 ); + } + arr = (count + 1) * MCPU_STARTUP_ARGV_SIZE; + + for( i = 0; i < (size_t)argc; ++i ) + { + size_t sl = strlen( argv[i] ); + __mpu_uint64_t z = (__mpu_uint64_t)sl + 1; + if( z == 0 || strings > UINT64_MAX - z ) + { + snprintf( err, es, "startup strings overflow" ); + return( -1 ); + } + strings += z; + } + if( MCPU_STARTUP_SIZE > UINT64_MAX - arr || + MCPU_STARTUP_SIZE + arr > UINT64_MAX - strings) + { + snprintf( err, es, "startup size overflow" ); + return( -1 ); + } + raw = MCPU_STARTUP_SIZE + arr + strings; + if( raw > stack_size ) + { + snprintf( err, es, "command line does not fit executable STACK" ); + return( -1 ); + } + + b = (stack_size - raw) & ~((__mpu_uint64_t)MCPU_STARTUP_ALIGN - 1); + if( b > stack_size || raw > stack_size - b ) + { + snprintf( err, es, "command line does not fit aligned executable STACK" ); + return( -1 ); + } + + memset( sp + (size_t)b, 0, (size_t)( stack_size - b ) ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_VERSION_OFF, + MCPU_STARTUP_VERSION_CURRENT); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_SIZE_OFF, + MCPU_STARTUP_SIZE); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGC_OFF, count ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGV_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, + b + MCPU_STARTUP_SIZE)); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_RESERVED_OFF, 0 ); + + { + __mpu_uint64_t pos = b + MCPU_STARTUP_SIZE + arr; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t z = strlen( argv[i] ) + 1; + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + i * 8 ), + MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, pos )); + memcpy( sp + (size_t)pos, argv[i], z ); + pos += z; + } + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + (size_t)argc * 8 ), 0 ); + } + + *base = b; + return( 0 ); +} + +static int +cpu_exec_load_v1( const char *path, int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + unsigned char *f = 0; + size_t fn = 0; + int data, rc = -1; + __mpu_uint64_t phoff, shoff, entry, init, life, fini; + __mpu_uint16_t phnum, shnum, shstrndx; + size_t i; + rph ph[8]; + int seen[8] = {0}; + int stacki = -1, heapi = -1, progi = -1, datai = -1; + rsh *sh = 0; + int nt = 0, nl = 0; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + const unsigned char *shstr; + size_t shstrn; + exec_section_ranges ranges = {0}; + int data_section_seen = 0; + __mpu_uint64_t data_section_begin = 0, data_section_size = 0; + + if( !path || !ld ) + return( -1 ); + memset( ld, 0, sizeof( *ld ) ); + + if( cpu_melf_readall( path, &f, &fn, err, es ) ) + return( -1 ); + if( validate_ident( f, fn, &data, err, es ) ) + goto out; + + phoff = cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, data ); + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, data ); + phnum = cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, data ); + shstrndx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + entry = cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, data ); + init = cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, data ); + life = cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, data ); + fini = cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, data ); + + if( !phnum || phnum > 8 || + !cpu_melf_span( fn, phoff, (__mpu_uint64_t)phnum *MELF64_PHDR_SIZE ) || + !shnum || shstrndx == MELF_SHN_UNDEF || shstrndx >= shnum || + !cpu_melf_span( fn, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE )) + { + snprintf( err, es, "bad MELF table ranges" ); + goto out; + } + + sh = ( rsh * ) calloc( shnum, sizeof( *sh ) ); + if( !sh ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < shnum; ++i ) + get_shdr( f + (size_t)shoff + i *MELF64_SHDR_SIZE, data, &sh[i] ); + + /* Section zero is the all-zero null entry. */ + if( sh[0].name || sh[0].type || sh[0].flags || sh[0].addr || sh[0].off || + sh[0].size || sh[0].link || sh[0].info || sh[0].align || sh[0].entsize) + { + snprintf( err, es, "section zero is not MELF_SHT_NULL/all-zero" ); + goto out; + } + + if( sh[shstrndx].type != MELF_SHT_STRTAB || sh[shstrndx].flags || + sh[shstrndx].addr || sh[shstrndx].link || sh[shstrndx].info || + sh[shstrndx].align != 1 || sh[shstrndx].entsize || + sh[shstrndx].size > SIZE_MAX || + !cpu_melf_span( fn, sh[shstrndx].off, sh[shstrndx].size )) + { + snprintf( err, es, "bad .shstrtab" ); + goto out; + } + shstr = f + (size_t)sh[shstrndx].off; + shstrn = (size_t)sh[shstrndx].size; + if( !shstrn || shstr[0] != 0 ) + { + snprintf( err, es, "bad .shstrtab contents" ); + goto out; + } + { + const char *shstrname; + if( name_at( shstr, shstrn, sh[shstrndx].name, &shstrname ) || + strcmp( shstrname, MELF_SEC_SHSTRTAB )) + { + snprintf( err, es, "section-name table is not canonical .shstrtab" ); + goto out; + } + } + + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( sh[i].type == MELF_SHT_RELA ) + { + snprintf( err, es, "ET_EXEC must not contain SHT_RELA sections" ); + goto out; + } + + if( sh[i].type == MELF_SHT_MCPU_TARGET ) + { + if( nt++ || strcmp( name, MELF_SEC_MCPU_TARGET ) || sh[i].flags || sh[i].addr || + sh[i].size != MELF_MCPU_TARGET_SIZE || sh[i].link || sh[i].info || + sh[i].align != 8 || sh[i].entsize != MELF_MCPU_TARGET_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_target( f + (size_t)sh[i].off, &mt, data ) || + cpu_melf_validate_target( &mt, data, err, es )) + goto out; + } + else if( sh[i].type == MELF_SHT_MCPU_LIFECYCLE ) + { + if( nl++ || strcmp( name, MELF_SEC_MCPU_LIFECYCLE ) || sh[i].flags || sh[i].addr || + sh[i].size != MELF_MCPU_LIFECYCLE_SIZE || sh[i].link || sh[i].info || + sh[i].align != 8 || sh[i].entsize != MELF_MCPU_LIFECYCLE_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_lifecycle( f + (size_t)sh[i].off, &ml, data ) || + cpu_melf_validate_lifecycle( &ml, err, es )) + goto out; + } + } + if( nt != 1 || nl != 1 ) + { + snprintf( err, es, + "MELF executable requires exactly one target and lifecycle metadata section"); + goto out; + } + + /* Validate Program Headers without allocating guest memory. */ + for( i = 0; i < phnum; ++i ) + { + unsigned seg; + get_phdr( f + (size_t)phoff + i *MELF64_PHDR_SIZE, data, &ph[i] ); + + if( ph[i].paddr ) + { + snprintf( err, es, "p_paddr must be zero" ); + goto out; + } + if( ph[i].align > 1 && !cpu_melf_is_pow2( ph[i].align ) ) + { + snprintf( err, es, "bad p_align" ); + goto out; + } + + if( ph[i].type == MELF_PT_LOAD ) + { + seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + if( !image_backed_seg( seg ) || MELF_MCPU_PTR64_OFFSET( ph[i].vaddr ) != 0 || + seen[seg]++ || ph[i].flags != (unsigned)expected_pflags( seg ) || + ph[i].filesz > ph[i].memsz || ph[i].maxsz != ph[i].memsz || + !cpu_melf_span( fn, ph[i].off, ph[i].filesz ) || + ph[i].memsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX || + (ph[i].align > 1 && ph[i].off % ph[i].align != 0)) + { + snprintf( err, es, "invalid PT_LOAD descriptor" ); + goto out; + } + if( seg == MELF_MCPU_SEG_PROGRAM ) + progi = (int)i; + if( seg == MELF_MCPU_SEG_DATA ) + datai = (int)i; + } + else if( ph[i].type == MELF_PT_MCPU_STACK ) + { + seg = MELF_MCPU_SEG_STACK; + if( seen[seg]++ || ph[i].vaddr != MELF_MCPU_VADDR_STACK || + ph[i].flags != MELF_PFLAGS_STACK || ph[i].off || ph[i].filesz || + !ph[i].memsz || ph[i].maxsz != ph[i].memsz || + ph[i].memsz % MCPU_STARTUP_ALIGN || ph[i].align < MCPU_STARTUP_ALIGN || + !cpu_melf_is_pow2( ph[i].align ) || + ph[i].memsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX) + { + snprintf( err, es, "invalid PT_MCPU_STACK descriptor" ); + goto out; + } + stacki = (int)i; + } + else if( ph[i].type == MELF_PT_MCPU_HEAP ) + { + seg = MELF_MCPU_SEG_HEAP; + if( seen[seg]++ || ph[i].vaddr != MELF_MCPU_VADDR_HEAP || + ph[i].flags != MELF_PFLAGS_HEAP || ph[i].off || ph[i].filesz || + ph[i].memsz < CPU_HEAP_BEGIN || ph[i].maxsz < ph[i].memsz || + ph[i].align < 16 || !cpu_melf_is_pow2( ph[i].align ) || + ph[i].maxsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX) + { + snprintf( err, es, "invalid PT_MCPU_HEAP descriptor" ); + goto out; + } + heapi = (int)i; + } + else if( ph[i].type != MELF_PT_NULL ) + { + snprintf( err, es, "unsupported program-header type" ); + goto out; + } + } + + if( progi < 0 || stacki < 0 ) + { + snprintf( err, es, "MELF executable requires PROGRAM and STACK descriptors" ); + goto out; + } + + if( datai >= 0 ) + { + if( ph[datai].memsz < CPU_DATA_NULL_GUARD_SIZE || + ph[datai].filesz < CPU_DATA_NULL_GUARD_SIZE) + { + snprintf( err, es, "DATA segment is too small for NULL guard" ); + goto out; + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i ) + if( f[(size_t)ph[datai].off + i] ) + { + snprintf( err, es, "DATA NULL guard is not zero" ); + goto out; + } + } + + /* Section-to-segment validation. */ + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( sh[i].flags & MELF_SHF_ALLOC ) + { + unsigned seg, flagseg; + size_t k; + int found = 0; + __mpu_uint64_t off; + + if( sh[i].type != MELF_SHT_PROGBITS && sh[i].type != MELF_SHT_NOBITS ) + { + snprintf( err, es, "allocated section has unsupported type: %s", name ); + goto out; + } + if( ( sh[i].flags & MELF_SHF_MCPU_SEG_VALID ) == 0 || + !sh[i].align || !cpu_melf_is_pow2( sh[i].align )) + { + snprintf( err, es, "invalid allocated section attributes: %s", name ); + goto out; + } + seg = MELF_MCPU_PTR64_SEG( sh[i].addr ); + flagseg = MELF_SHF_MCPU_GET_SEG( sh[i].flags ); + if( !image_backed_seg( seg ) || flagseg != seg || + sh[i].flags != expected_shflags( seg )) + { + snprintf( err, es, "allocated section segment/flags disagree: %s", name ); + goto out; + } + if( sh[i].type == MELF_SHT_NOBITS && + ( seg != MELF_MCPU_SEG_DATA || strcmp( name, MELF_SEC_BSS ) )) + { + snprintf( err, es, "ABI-v1 SHT_NOBITS is only canonical .bss in DATA" ); + goto out; + } + + off = MELF_MCPU_PTR64_OFFSET( sh[i].addr ); + for( k = 0; k < phnum; ++k ) + if( ph[k].type == MELF_PT_LOAD && + MELF_MCPU_PTR64_SEG( ph[k].vaddr ) == seg) + { + if( off > ph[k].memsz || sh[i].size > ph[k].memsz - off || + sh[i].align > nzalign( ph[k].align )) + { + snprintf( err, es, "allocated section outside/incompatible with PT_LOAD" ); + goto out; + } + if( sh[i].type == MELF_SHT_PROGBITS ) + { + if( sh[i].off < ph[k].off || off > ph[k].filesz || + sh[i].off - ph[k].off != off || + sh[i].size > ph[k].filesz - off || + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "section/file mapping disagrees with PT_LOAD" ); + goto out; + } + } + found = 1; + break; + } + if( !found ) + { + snprintf( err, es, "allocated section has no PT_LOAD" ); + goto out; + } + + if( seg == MELF_MCPU_SEG_DATA && !strcmp( name, MELF_SEC_DATA ) ) + { + if( sh[i].type != MELF_SHT_PROGBITS || data_section_seen++ ) + { + snprintf( err, es, "invalid or duplicate .data section" ); + goto out; + } + data_section_begin = off; + data_section_size = sh[i].size; + } + + if( seg == MELF_MCPU_SEG_PROGRAM && + record_lifecycle_section( &ranges, name, off, sh[i].size, err, es )) + goto out; + } + else if( sh[i].type == MELF_SHT_MCPU_TARGET || + sh[i].type == MELF_SHT_MCPU_LIFECYCLE) + { + if( sh[i].addr ) + { + snprintf( err, es, "metadata section must not have guest address" ); + goto out; + } + } + } + + /* Lifecycle entries are numeric Ehdr authority; sections validate their role. */ + if( !entry || !life || + MELF_MCPU_PTR64_SEG( entry ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_SEG( life ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( entry ) >= ph[progi].memsz || + MELF_MCPU_PTR64_OFFSET( life ) >= ph[progi].memsz) + { + snprintf( err, es, "invalid mandatory lifecycle entry" ); + goto out; + } + if( !ranges.has_text || + !range_contains( ranges.text_begin, ranges.text_end, + MELF_MCPU_PTR64_OFFSET( life ))) + { + snprintf( err, es, "e_life_entry is not inside .text" ); + goto out; + } + + if( init ) + { + if( MELF_MCPU_PTR64_SEG( init ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( init ) >= ph[progi].memsz || init != entry || + !ranges.has_init || + !range_contains( ranges.init_begin, ranges.init_end, + MELF_MCPU_PTR64_OFFSET( entry ))) + { + snprintf( err, es, "invalid INIT entry" ); + goto out; + } + } + else + { + if( entry != life || ranges.has_init ) + { + snprintf( err, es, "without INIT, e_entry must equal e_life_entry and .init must be absent" ); + goto out; + } + } + + if( fini ) + { + if( MELF_MCPU_PTR64_SEG( fini ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( fini ) >= ph[progi].memsz || + !ranges.has_fini || + !range_contains( ranges.fini_begin, ranges.fini_end, + MELF_MCPU_PTR64_OFFSET( fini ))) + { + snprintf( err, es, "invalid FINI entry" ); + goto out; + } + } + else if( ranges.has_fini ) + { + snprintf( err, es, ".fini exists but e_fini_entry is zero" ); + goto out; + } + + /* Validation complete: only now instantiate guest memory. */ + for( i = 0; i < phnum; ++i ) + if( ph[i].type == MELF_PT_LOAD ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + __mpu_UNIT_t *dst; + if( seg_alloc( seg, ph[i].memsz, ph[i].maxsz, &dst ) ) + { + snprintf( err, es, "cannot allocate guest segment %u", seg ); + goto out; + } + if( ph[i].memsz ) + memset( dst, 0, (size_t)ph[i].memsz ); + if( ph[i].filesz ) + memcpy( dst, f + (size_t)ph[i].off, (size_t)ph[i].filesz ); + } + + { + __mpu_UNIT_t *dst; + if( seg_alloc( MELF_MCPU_SEG_STACK, ph[stacki].memsz, ph[stacki].memsz, &dst ) ) + { + snprintf( err, es, "cannot allocate STACK" ); + goto out; + } + (void)dst; + if( heapi >= 0 && + seg_alloc( MELF_MCPU_SEG_HEAP, ph[heapi].memsz, ph[heapi].maxsz, &dst )) + { + snprintf( err, es, "cannot allocate HEAP" ); + goto out; + } + } + + if( put_startup( argc, argv, ph[stacki].memsz, &ld->startup_base, err, es ) ) + goto out; + if( __cpu_program_protect() ) + { + snprintf( err, es, "cannot protect PROGRAM" ); + goto out; + } + + ld->stack_size = ph[stacki].memsz; + ld->has_data_section = data_section_seen; + ld->data_section_begin = data_section_begin; + ld->data_section_size = data_section_size; + ld->start_entry = MELF_MCPU_PTR64_OFFSET( entry ); + ld->life_entry = MELF_MCPU_PTR64_OFFSET( life ); + ld->has_init = init != 0; + ld->init_entry = init ? MELF_MCPU_PTR64_OFFSET( init ) : 0; + ld->has_fini = fini != 0; + ld->fini_entry = fini ? MELF_MCPU_PTR64_OFFSET( fini ) : 0; + + __cpu_pc = ld->start_entry; + __cpu_sp = ld->startup_base; + __cpu_fp = ld->stack_size; + rc = 0; + +out: + free( f ); + free( sh ); + return( rc ); +} + + +int +cpu_exec_abi_version( const char *path, unsigned *abi, char *err, size_t es ) +{ + FILE *fp; + unsigned char h[MELF_EI_NIDENT]; + size_t n; + + fp = fopen( path, "rb" ); + if( !fp ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + n = fread( h, 1, sizeof( h ), fp ); + if( fclose( fp ) != 0 && n == sizeof( h ) ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( n != sizeof( h ) || h[MELF_EI_MAG0] != MELFMAG0 || + h[MELF_EI_MAG1] != MELFMAG1 || h[MELF_EI_MAG2] != MELFMAG2 || + h[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "%s: not a MELF image", path ); + return( -1 ); + } + *abi = h[MELF_EI_ABIVERSION]; + return( 0 ); +} + +int +cpu_exec_load_preload( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + unsigned abi; + if( cpu_exec_abi_version( path, &abi, err, es ) ) + return( -1 ); + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( preload_list && *preload_list ) + { + snprintf( err, es, + "--ldpreload requires an ABI-v2/ABI-v3 executable linked with mcpu-ld --dynamic"); + return( -1 ); + } + return( cpu_exec_load_v1( path, argc, argv, ld, err, es ) ); + } + if( abi == MELF_ABIVERSION_MCPU_2 || + abi == MELF_ABIVERSION_MCPU_3 ) + return( cpu_dyn_load( path, preload_list, argc, argv, ld, err, es ) ); + snprintf( err, es, "unsupported MELF ABI version %u", abi ); + return( -1 ); +} + + +int +cpu_exec_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + unsigned abi; + + if( cpu_exec_abi_version(path, &abi, err, es) ) + return( -1 ); + + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( module_list != NULL && *module_list != 0 ) + { + snprintf( err, es, + "dynamic modules require an ABI-v2/ABI-v3 executable linked with mcpu-ld --dynamic" ); + return( -1 ); + } + return( cpu_exec_load_v1(path, argc, argv, ld, err, es) ); + } + + if( abi == MELF_ABIVERSION_MCPU_2 || + abi == MELF_ABIVERSION_MCPU_3 ) + return( cpu_dyn_load_resolved(path, module_list, preload_count, + argc, argv, ld, err, es) ); + + snprintf( err, es, "unsupported MELF ABI version %u", abi ); + return( -1 ); + +} /* End of cpu_exec_load_resolved() */ + +int +cpu_exec_load( const char *path, int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + return( cpu_exec_load_preload( path, NULL, argc, argv, ld, err, es ) ); +} diff --git a/utils/mcpu-exec.h b/utils/mcpu-exec.h new file mode 100644 index 0000000..54c206f --- /dev/null +++ b/utils/mcpu-exec.h @@ -0,0 +1,42 @@ +#ifndef __CPU_EXEC_H +#define __CPU_EXEC_H +#include <stddef.h> +#include "mcpu-asm.h" + +typedef struct cpu_exec_options +{ + __mpu_uint64_t stack_size; + int have_heap; + __mpu_uint64_t heap_size; + __mpu_uint64_t heap_limit; +} cpu_exec_options; + +typedef struct cpu_exec_loaded +{ + int has_init; + int has_fini; + __mpu_uint64_t start_entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + __mpu_uint64_t startup_base; + __mpu_uint64_t stack_size; + int has_data_section; + __mpu_uint64_t data_section_begin; + __mpu_uint64_t data_section_size; +} cpu_exec_loaded; + +int cpu_exec_write( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, char *err, size_t errsz); +int cpu_exec_load( const char *path, int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_exec_abi_version( const char *path, unsigned *abi, + char *err, size_t errsz ); +int cpu_exec_load_preload( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_exec_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz ); +#endif diff --git a/utils/mcpu-ld-options.c b/utils/mcpu-ld-options.c new file mode 100644 index 0000000..6b64654 --- /dev/null +++ b/utils/mcpu-ld-options.c @@ -0,0 +1,463 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> + +#include "mcpu-ld-options.h" + +static int +parse_size( const char *s, __mpu_uint64_t *v ) +{ + unsigned long long x; + __mpu_uint64_t m = 1; + char *e; + + if( !s || !*s || *s == '-' ) return( -1 ); + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s ) return( -1 ); + + if( *e == '\0' ) + m = 1; + else if( !strcmp( e, "K" ) || !strcmp( e, "KiB" ) ) + m = __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "M" ) || !strcmp( e, "MiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "G" ) || !strcmp( e, "GiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else + return( -1 ); + + if( (__mpu_uint64_t)x != x || (__mpu_uint64_t)x > UINT64_MAX / m ) + return( -1 ); + + *v = (__mpu_uint64_t)x * m; + return( 0 ); + +} /* End of parse_size() */ + + +static int +append_input( mcpu_ld_options *options, char *name ) +{ + char **p; + size_t capacity; + + if( options->input_count == options->input_capacity ) + { + capacity = options->input_capacity ? options->input_capacity * 2U : 8U; + if( capacity < options->input_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( options->inputs, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + options->inputs = p; + options->input_capacity = capacity; + } + + options->inputs[options->input_count++] = name; + return( 0 ); + +} /* End of append_input() */ + + +static int +append_library( mcpu_ld_options *options, char *name ) +{ + char **p; + size_t capacity; + + if( options->library_count == options->library_capacity ) + { + capacity = options->library_capacity ? options->library_capacity * 2U : 8U; + if( capacity < options->library_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( options->libraries, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + options->libraries = p; + options->library_capacity = capacity; + } + + options->libraries[options->library_count++] = name; + return( 0 ); + +} /* End of append_library() */ + + +void +mcpu_ld_options_init( mcpu_ld_options *options, const char *progname ) +{ + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + mcpu_tool_options_init( &options->common, progname ); + options->exec.stack_size = 1048576U; + options->exec.have_heap = 1; + options->exec.heap_size = 1048576U; + options->exec.heap_limit = 1048576U; + options->mode = CPU_LINK_STATIC_EXEC; + +} /* End of mcpu_ld_options_init() */ + + +void +mcpu_ld_options_free( mcpu_ld_options *options ) +{ + if( options == NULL ) + return; + + free( options->inputs ); + options->inputs = NULL; + options->input_count = 0; + options->input_capacity = 0; + free( options->libraries ); + options->libraries = NULL; + options->library_count = 0; + options->library_capacity = 0; + mcpu_tool_options_free( &options->common ); + +} /* End of mcpu_ld_options_free() */ + + +void +mcpu_ld_usage( const mcpu_ld_options *options ) +{ + const char *name = options ? options->common.progname : "mcpu-ld"; + + fprintf( stderr, + "usage: %s [link-options] -o output input.o ...\n\n" + "Link LibMCPU MELF ET_REL object files.\n\n" + "Output mode (at most one):\n" + " -r - produce one partial-link MELF ET_REL object\n" + " --shared - produce ABI-v3 MELF ET_DYN shared object\n" + " --dynamic - produce ABI-v3 startup-dynamic MELF ET_EXEC\n" + " (default without these options: ABI-v1 static ET_EXEC)\n\n" + "MCPU system options:\n" + " --no-config - do not read mcpu-ld.conf files\n" + " --sys-root=PATH - use PATH as MCPU root and do not read config files\n" + " --dump-config - print effective MCPU configuration and exit\n" + " --print-search-dirs - print effective library search directories and exit\n\n" + "Library dependency options:\n" + " -L <dir>, -L<dir> - add explicit MCPU library search directory\n" + " -l <name>, -l<name> - link against lib<name>; ABI-v1 and -r search .a,\n" + " ABI-v3 searches .so then .a in each directory\n" + " --static - force every -l<name> lookup to use lib<name>.a\n" + " without changing the selected output mode\n" + " (selected .so files alone create DT_NEEDED)\n\n" + "Shared-library identity:\n" + " --soname=NAME - set mandatory ABI-v3 ET_DYN SONAME\n" + " --soname NAME - NAME must be a logical name without '/'\n\n" + "Link options:\n" + " -o <file> - write output to <file>\n" + " --stack-size=<size> - set fixed STACK size; positive and %u-byte aligned\n" + " (default for executables: 1048576)\n" + " --heap-size=<size> - set initial HEAP size (default: 1048576)\n" + " --heap-limit=<size> - set immutable maximum HEAP size (default: 1048576)\n" + " --no-heap - omit PT_MCPU_HEAP from an executable\n" + " -v, --verbose - print output-object summary\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n\n" + "<size> is bytes by default; K/KiB, M/MiB and G/GiB suffixes are accepted.\n" + "K, M and G use binary multipliers (1024, 1024^2 and 1024^3).\n\n" + "Partial -r output is MELF ET_REL: STACK/HEAP/SONAME and shared objects are\n" + "not part of this mode; unresolved symbols and RELA records are preserved.\n" + "Shared objects may contain only .text/.data/.bss allocated sections,\n" + "must define --soname and must not use STACK/HEAP sizing options. ABI-v3\n" + "dynamic executables retain preemptible GLOBAL/WEAK relocations; mcpu-run\n" + "continues to accept explicit --ldpreload and legacy ABI-v2 files.\n", + name, CPU_ABI_STACK_ALIGN ); + +} /* End of mcpu_ld_usage() */ + + +int +mcpu_ld_handle_options( mcpu_ld_options *options, int argc, char **argv ) +{ + int i; + + if( options == NULL || argv == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( i = 1; i < argc; ++i ) + { + int common; + + if( !strcmp( argv[i], "-h" ) || !strcmp( argv[i], "--help" ) ) + { + options->common.help = 1; + return( 0 ); + } + + common = mcpu_tool_parse_common_option( &options->common, + argc, argv, &i ); + if( common < 0 ) + return( -1 ); + if( common > 0 ) + { + if( options->common.version_action != MCPU_VERSION_NONE ) + return( 0 ); + continue; + } + + if( !strcmp( argv[i], "-v" ) || !strcmp( argv[i], "--verbose" ) ) + { + options->verbose = 1; + } + else if( !strcmp( argv[i], "-r" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_RELOCATABLE; + } + else if( !strcmp( argv[i], "--shared" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_SHARED; + } + else if( !strcmp( argv[i], "--dynamic" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_DYNAMIC_EXEC; + } + else if( !strcmp( argv[i], "--static" ) ) + { + options->force_static = 1; + } + else if( !strncmp( argv[i], "--soname=", 9 ) ) + { + const char *name = argv[i] + 9; + + if( options->soname != NULL || name[0] == 0 || + strchr( name, '/' ) != NULL ) + { + fprintf( stderr, "%s: invalid or duplicate --soname\n", + options->common.progname ); + return( -1 ); + } + options->soname = name; + } + else if( !strcmp( argv[i], "--soname" ) ) + { + const char *name; + + if( ++i >= argc ) + { + fprintf( stderr, "%s: argument missing after --soname\n", + options->common.progname ); + return( -1 ); + } + name = argv[i]; + if( options->soname != NULL || name[0] == 0 || + strchr( name, '/' ) != NULL ) + { + fprintf( stderr, "%s: invalid or duplicate --soname\n", + options->common.progname ); + return( -1 ); + } + options->soname = name; + } + else if( !strcmp( argv[i], "-L" ) ) + { + if( ++i >= argc || argv[i][0] == 0 || + mcpu_tool_add_library_directory(&options->common, argv[i]) != 0 ) + { + fprintf( stderr, "%s: argument missing or invalid after -L\n", + options->common.progname ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "-L", 2 ) ) + { + if( argv[i][2] == 0 || + mcpu_tool_add_library_directory(&options->common, argv[i] + 2) != 0 ) + { + fprintf( stderr, "%s: bad -L directory: %s\n", + options->common.progname, argv[i] + 2 ); + return( -1 ); + } + } + else if( !strcmp( argv[i], "-l" ) ) + { + char *name; + + if( ++i >= argc ) + { + fprintf( stderr, "%s: argument missing after -l\n", + options->common.progname ); + return( -1 ); + } + name = argv[i]; + if( name[0] == 0 || strchr(name, '/') != NULL || + append_library(options, name) != 0 ) + { + fprintf( stderr, "%s: invalid library name after -l: %s\n", + options->common.progname, name ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "-l", 2 ) ) + { + char *name = argv[i] + 2; + + if( name[0] == 0 || strchr(name, '/') != NULL || + append_library(options, name) != 0 ) + { + fprintf( stderr, "%s: invalid library option: %s\n", + options->common.progname, argv[i] ); + return( -1 ); + } + } + else if( !strcmp( argv[i], "-o" ) ) + { + if( ++i >= argc || options->out_fname != NULL ) + { + mcpu_ld_usage( options ); + return( -1 ); + } + options->out_fname = argv[i]; + } + else if( !strncmp( argv[i], "--stack-size=", 13 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 13, &options->exec.stack_size) || + !options->exec.stack_size || + options->exec.stack_size % CPU_ABI_STACK_ALIGN ) + { + fprintf( stderr, "%s: stack size must be a positive multiple of %u\n", + options->common.progname, CPU_ABI_STACK_ALIGN ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "--heap-size=", 12 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 12, &options->exec.heap_size) || + options->exec.heap_size < CPU_HEAP_BEGIN ) + { + fprintf( stderr, "%s: heap size must be at least %u\n", + options->common.progname, CPU_HEAP_BEGIN ); + return( -1 ); + } + options->exec.have_heap = 1; + } + else if( !strncmp( argv[i], "--heap-limit=", 13 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 13, &options->exec.heap_limit) || + options->exec.heap_limit < CPU_HEAP_BEGIN ) + { + fprintf( stderr, "%s: bad heap limit\n", options->common.progname ); + return( -1 ); + } + options->exec.have_heap = 1; + } + else if( !strcmp( argv[i], "--no-heap" ) ) + { + options->size_option_seen = 1; + options->exec.have_heap = 0; + options->exec.heap_size = options->exec.heap_limit = 0; + } + else if( argv[i][0] == '-' ) + { + fprintf( stderr, "%s: unknown option %s\n", + options->common.progname, argv[i] ); + return( -1 ); + } + else if( append_input(options, argv[i]) != 0 ) + { + perror( "realloc" ); + return( -1 ); + } + } + + if( options->common.help || + mcpu_tool_has_information_action(&options->common) ) + return( 0 ); + + if( options->out_fname == NULL || options->input_count == 0 ) + { + mcpu_ld_usage( options ); + return( -1 ); + } + + if( options->mode == CPU_LINK_RELOCATABLE && options->size_option_seen ) + { + fprintf( stderr, "%s: STACK/HEAP options are invalid with -r\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode == CPU_LINK_SHARED && options->size_option_seen ) + { + fprintf( stderr, "%s: STACK/HEAP options are invalid with --shared\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode == CPU_LINK_SHARED && options->soname == NULL ) + { + fprintf( stderr, "%s: ABI-v3 --shared requires --soname=NAME\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode != CPU_LINK_SHARED && options->soname != NULL ) + { + fprintf( stderr, "%s: --soname is valid only with --shared\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode != CPU_LINK_SHARED && + options->mode != CPU_LINK_RELOCATABLE && + options->exec.have_heap && + options->exec.heap_limit < options->exec.heap_size ) + { + fprintf( stderr, "%s: heap limit is smaller than heap size\n", + options->common.progname ); + return( -1 ); + } + + return( 0 ); + +} /* End of mcpu_ld_handle_options() */ diff --git a/utils/mcpu-ld-options.h b/utils/mcpu-ld-options.h new file mode 100644 index 0000000..b72a92e --- /dev/null +++ b/utils/mcpu-ld-options.h @@ -0,0 +1,36 @@ +#ifndef __MCPU_LD_OPTIONS_H__ +#define __MCPU_LD_OPTIONS_H__ 1 + +#include <stddef.h> + +#include "mcpu-exec.h" +#include "mcpu-link.h" +#include "mcpu-tool-options.h" + +typedef struct mcpu_ld_options mcpu_ld_options; +struct mcpu_ld_options +{ + mcpu_tool_options common; + const char *out_fname; + cpu_exec_options exec; + cpu_link_mode mode; + int mode_seen; + int force_static; + int verbose; + int size_option_seen; + const char *soname; + char **inputs; + size_t input_count; + size_t input_capacity; + char **libraries; + size_t library_count; + size_t library_capacity; +}; + +void mcpu_ld_options_init( mcpu_ld_options *options, const char *progname ); +void mcpu_ld_options_free( mcpu_ld_options *options ); +int mcpu_ld_handle_options( mcpu_ld_options *options, + int argc, char **argv ); +void mcpu_ld_usage( const mcpu_ld_options *options ); + +#endif /* __MCPU_LD_OPTIONS_H__ */ diff --git a/utils/mcpu-ld.c b/utils/mcpu-ld.c new file mode 100644 index 0000000..cf793ea --- /dev/null +++ b/utils/mcpu-ld.c @@ -0,0 +1,1038 @@ +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> + +#include "mcpu-asm.h" +#include "mcpu-object.h" +#include "mcpu-link.h" +#include "mcpu-exec.h" +#include "mcpu-dyn.h" +#include "mcpu-archive.h" +#include "mcpu-ld-options.h" + + +typedef struct mcpu_ld_library +{ + char *path; + cpu_dyn_library_info info; +} mcpu_ld_library; + + +typedef enum mcpu_hll_program_kind +{ + MCPU_HLL_NONE = 0, + MCPU_HLL_PROGRAM, + MCPU_HLL_MODEL +} mcpu_hll_program_kind; + + +static void +free_libraries( mcpu_ld_library *libraries, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + free( libraries[i].path ); + cpu_dyn_library_free( &libraries[i].info ); + } + free( libraries ); + +} /* End of free_libraries() */ + + +static int +find_soname( const mcpu_ld_library *libraries, size_t count, + const char *soname ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + if( strcmp(libraries[i].info.soname, soname) == 0 ) + return( (int)i ); + + return( -1 ); + +} /* End of find_soname() */ + + +static int +append_link_object( cpu_asm_object **objects, size_t *count, size_t *capacity, + cpu_asm_object *object, char *err, size_t es ) +{ + cpu_asm_object *p; + size_t c; + + if( *count == *capacity ) + { + c = *capacity ? *capacity * 2U : 8U; + if( c < *capacity || c > SIZE_MAX / sizeof( *p ) ) + { + snprintf( err, es, "too many input objects" ); + return( -1 ); + } + + p = (cpu_asm_object *)realloc( *objects, c * sizeof( *p ) ); + if( p == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + *objects = p; + *capacity = c; + } + + (*objects)[*count] = *object; + memset( object, 0, sizeof( *object ) ); + ++*count; + return( 0 ); + +} /* End of append_link_object() */ + + +static int +hll_symbol_is_strong_func( const cpu_asm_symbol *symbol ) +{ + return( symbol != NULL && symbol->section != CPU_OBJ_SEC_UNDEF && + symbol->binding == CPU_OBJ_BIND_GLOBAL && + symbol->type == CPU_OBJ_TYPE_FUNC ); + +} /* End of hll_symbol_is_strong_func() */ + + +static int +hll_record_definition( const cpu_asm_object *objects, size_t object_index, + size_t symbol_index, size_t *definition_object, + const cpu_asm_symbol **definition_symbol, + char *err, size_t es ) +{ + const cpu_asm_symbol *symbol = &objects[object_index].symbols[symbol_index]; + + if( *definition_symbol != NULL ) + { + snprintf( err, es, "multiple HLL complete-program definitions of %s", + symbol->name ); + return( -1 ); + } + + *definition_object = object_index; + *definition_symbol = symbol; + return( 0 ); + +} /* End of hll_record_definition() */ + + +static int +classify_hll_program( const cpu_asm_object *objects, size_t count, + mcpu_hll_program_kind *kind, + char *err, size_t es ) +{ + const cpu_asm_symbol *main_symbol = NULL; + const cpu_asm_symbol *init_symbol = NULL; + const cpu_asm_symbol *life_symbol = NULL; + const cpu_asm_symbol *fini_symbol = NULL; + size_t main_object = 0, init_object = 0, life_object = 0, fini_object = 0; + size_t i, j; + int raw_lifecycle = 0; + + *kind = MCPU_HLL_NONE; + + for( i = 0; i < count; ++i ) + for( j = 0; j < objects[i].symbol_count; ++j ) + { + const cpu_asm_symbol *symbol = &objects[i].symbols[j]; + + if( symbol->section == CPU_OBJ_SEC_UNDEF ) + continue; + + if( !strncmp(symbol->name, "__mcpu_crt_", 11) ) + { + snprintf( err, es, "input object defines reserved CRT symbol: %s", + symbol->name ); + return( -1 ); + } + + if( !strcmp(symbol->name, "_start") || + !strcmp(symbol->name, "_life") || + !strcmp(symbol->name, "_fini") ) + { + raw_lifecycle = 1; + continue; + } + + if( symbol->binding == CPU_OBJ_BIND_LOCAL ) + continue; + + if( !strcmp(symbol->name, "main") ) + { + if( hll_record_definition(objects, i, j, &main_object, &main_symbol, + err, es) ) + return( -1 ); + } + else if( !strcmp(symbol->name, "init") ) + { + if( hll_record_definition(objects, i, j, &init_object, &init_symbol, + err, es) ) + return( -1 ); + } + else if( !strcmp(symbol->name, "life") ) + { + if( hll_record_definition(objects, i, j, &life_object, &life_symbol, + err, es) ) + return( -1 ); + } + else if( !strcmp(symbol->name, "fini") ) + { + if( hll_record_definition(objects, i, j, &fini_object, &fini_symbol, + err, es) ) + return( -1 ); + } + } + + if( raw_lifecycle && + (main_symbol != NULL || init_symbol != NULL || life_symbol != NULL) ) + { + snprintf( err, es, "cannot mix low-level lifecycle with HLL complete-program definitions" ); + return( -1 ); + } + + if( main_symbol != NULL ) + { + if( init_symbol != NULL || life_symbol != NULL ) + { + snprintf( err, es, "HLL PROGRAM main is incompatible with MODEL init/life" ); + return( -1 ); + } + if( !hll_symbol_is_strong_func(main_symbol) ) + { + snprintf( err, es, "HLL PROGRAM main must be strong GLOBAL/FUNC" ); + return( -1 ); + } + if( fini_symbol == NULL || fini_object != main_object || + !hll_symbol_is_strong_func(fini_symbol) ) + { + snprintf( err, es, + "HLL PROGRAM requires strong GLOBAL/FUNC main and fini in one ET_REL" ); + return( -1 ); + } + *kind = MCPU_HLL_PROGRAM; + return( 0 ); + } + + if( init_symbol != NULL || life_symbol != NULL ) + { + if( init_symbol == NULL || life_symbol == NULL ) + { + snprintf( err, es, "HLL MODEL requires both init and life" ); + return( -1 ); + } + if( init_object != life_object || fini_symbol == NULL || + fini_object != init_object || + !hll_symbol_is_strong_func(init_symbol) || + !hll_symbol_is_strong_func(life_symbol) || + !hll_symbol_is_strong_func(fini_symbol) ) + { + snprintf( err, es, + "HLL MODEL requires strong GLOBAL/FUNC init, life and fini in one ET_REL" ); + return( -1 ); + } + *kind = MCPU_HLL_MODEL; + } + + return( 0 ); + +} /* End of classify_hll_program() */ + + +static char * +hll_crt_path( const mcpu_ld_options *options, const char *name ) +{ + const char *root; + char *path; + size_t n; + + root = options->common.runtime.installation_root; + if( root == NULL || *root == 0 || name == NULL || *name == 0 ) + return( NULL ); + + if( strlen(root) > SIZE_MAX - strlen(name) - sizeof("/lib/mcpu/") ) + return( NULL ); + + n = strlen(root) + strlen(name) + sizeof("/lib/mcpu/"); + path = (char *)malloc( n ); + if( path != NULL ) + snprintf( path, n, "%s/lib/mcpu/%s", root, name ); + + return( path ); + +} /* End of hll_crt_path() */ + + +static int +read_hll_crt_object( const mcpu_ld_options *options, const char *name, + cpu_asm_object *object, char *err, size_t es ) +{ + char detail[512]; + char *path; + + memset( object, 0, sizeof( *object ) ); + path = hll_crt_path( options, name ); + if( path == NULL ) + { + snprintf( err, es, "cannot construct HLL CRT path for %s", name ); + return( -1 ); + } + + if( cpu_object_read(path, object, detail, sizeof(detail)) ) + { + snprintf( err, es, "cannot load HLL CRT object %.120s: %.300s", + path, detail ); + free( path ); + return( -1 ); + } + + free( path ); + return( 0 ); + +} /* End of read_hll_crt_object() */ + + +static int +inject_hll_crt( const mcpu_ld_options *options, mcpu_hll_program_kind kind, + cpu_asm_object **objects, size_t *count, size_t *capacity, + char *err, size_t es ) +{ + cpu_asm_object adapter, crti, crtn; + cpu_asm_object *combined; + const char *adapter_name; + size_t new_count; + + memset( &adapter, 0, sizeof(adapter) ); + memset( &crti, 0, sizeof(crti) ); + memset( &crtn, 0, sizeof(crtn) ); + + adapter_name = kind == MCPU_HLL_PROGRAM ? "crt0.o" : "crt1.o"; + if( read_hll_crt_object(options, adapter_name, &adapter, err, es) || + read_hll_crt_object(options, "crti.o", &crti, err, es) || + read_hll_crt_object(options, "crtn.o", &crtn, err, es) ) + goto bad; + + if( *count > SIZE_MAX - 3 || + *count + 3 > SIZE_MAX / sizeof(*combined) ) + { + snprintf( err, es, "too many input objects after HLL CRT insertion" ); + goto bad; + } + + new_count = *count + 3; + combined = (cpu_asm_object *)calloc( new_count, sizeof(*combined) ); + if( combined == NULL ) + { + snprintf( err, es, "out of memory" ); + goto bad; + } + + combined[0] = adapter; + combined[1] = crti; + if( *count != 0 ) + memcpy( combined + 2, *objects, *count * sizeof(*combined) ); + combined[new_count - 1] = crtn; + + memset( &adapter, 0, sizeof(adapter) ); + memset( &crti, 0, sizeof(crti) ); + memset( &crtn, 0, sizeof(crtn) ); + free( *objects ); + *objects = combined; + *count = new_count; + *capacity = new_count; + return( 0 ); + +bad: + cpu_asm_object_free( &adapter ); + cpu_asm_object_free( &crti ); + cpu_asm_object_free( &crtn ); + return( -1 ); + +} /* End of inject_hll_crt() */ + + +static int +object_defines_symbol( const cpu_asm_object *objects, size_t count, + const char *name ) +{ + size_t i, j; + + for( i = 0; i < count; ++i ) + for( j = 0; j < objects[i].symbol_count; ++j ) + { + const cpu_asm_symbol *s = &objects[i].symbols[j]; + + if( s->section != CPU_OBJ_SEC_UNDEF && + s->binding != CPU_OBJ_BIND_LOCAL && + strcmp(s->name, name) == 0 ) + return( 1 ); + } + + return( 0 ); + +} /* End of object_defines_symbol() */ + + +static int +library_defines_symbol( const mcpu_ld_library *libraries, size_t count, + const char *name ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + if( cpu_dyn_library_find_symbol(&libraries[i].info, name, NULL) == 0 ) + return( 1 ); + + return( 0 ); + +} /* End of library_defines_symbol() */ + + +static int +name_in_list( const char *const *names, size_t count, const char *name ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + if( strcmp(names[i], name) == 0 ) + return( 1 ); + + return( 0 ); + +} /* End of name_in_list() */ + + +static int +collect_strong_unresolved( const cpu_asm_object *objects, size_t object_count, + const mcpu_ld_library *libraries, + size_t library_count, + const char ***names_out, size_t *count_out, + char *err, size_t es ) +{ + const char **names = NULL; + size_t count = 0; + size_t i, j; + + *names_out = NULL; + *count_out = 0; + + for( i = 0; i < object_count; ++i ) + for( j = 0; j < objects[i].symbol_count; ++j ) + { + const cpu_asm_symbol *s = &objects[i].symbols[j]; + const char **p; + + if( s->section != CPU_OBJ_SEC_UNDEF || + s->binding != CPU_OBJ_BIND_GLOBAL ) + continue; + + if( object_defines_symbol(objects, object_count, s->name) || + library_defines_symbol(libraries, library_count, s->name) || + name_in_list(names, count, s->name) ) + continue; + + if( count == SIZE_MAX / sizeof( *p ) ) + { + free( names ); + snprintf( err, es, "too many unresolved symbols" ); + return( -1 ); + } + p = (const char **)realloc( names, (count + 1) * sizeof( *p ) ); + if( p == NULL ) + { + free( names ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + names = p; + names[count++] = s->name; + } + + *names_out = names; + *count_out = count; + return( 0 ); + +} /* End of collect_strong_unresolved() */ + + +static int +archive_member_is_needed( const mcpu_archive_member *member, + const char *const *unresolved, + size_t unresolved_count ) +{ + size_t i, j; + + for( i = 0; i < member->symbol_count; ++i ) + for( j = 0; j < unresolved_count; ++j ) + if( strcmp(member->symbols[i], unresolved[j]) == 0 ) + return( 1 ); + + return( 0 ); + +} /* End of archive_member_is_needed() */ + + +static int +extract_archive_members( const char *path, const mcpu_archive *archive, + cpu_asm_object **objects, size_t *object_count, + size_t *object_capacity, + const mcpu_ld_library *libraries, + size_t library_count, + char *err, size_t es ) +{ + unsigned char *selected = NULL; + int changed; + size_t i; + + if( archive->member_count != 0 ) + { + selected = (unsigned char *)calloc( archive->member_count, 1 ); + if( selected == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + + do + { + const char **unresolved = NULL; + size_t unresolved_count = 0; + + changed = 0; + if( collect_strong_unresolved(*objects, *object_count, + libraries, library_count, + &unresolved, &unresolved_count, + err, es) ) + { + free( selected ); + return( -1 ); + } + + for( i = 0; i < archive->member_count && unresolved_count != 0; ++i ) + { + cpu_asm_object object; + char detail[512]; + + if( selected[i] || + !archive_member_is_needed(&archive->members[i], + unresolved, unresolved_count) ) + continue; + + memset( &object, 0, sizeof( object ) ); + if( cpu_object_read_data(archive->members[i].data, + archive->members[i].size, + &object, detail, sizeof(detail)) ) + { + snprintf( err, es, "%.160s(%.120s): %.180s", + path, archive->members[i].name, detail ); + free( unresolved ); + free( selected ); + return( -1 ); + } + + if( append_link_object(objects, object_count, object_capacity, + &object, err, es) ) + { + cpu_asm_object_free( &object ); + free( unresolved ); + free( selected ); + return( -1 ); + } + + selected[i] = 1; + changed = 1; + + free( unresolved ); + unresolved = NULL; + unresolved_count = 0; + if( collect_strong_unresolved(*objects, *object_count, + libraries, library_count, + &unresolved, &unresolved_count, + err, es) ) + { + free( selected ); + return( -1 ); + } + } + + free( unresolved ); + } + while( changed ); + + free( selected ); + return( 0 ); + +} /* End of extract_archive_members() */ + + +static char * +library_filename( const char *name, const char *suffix ) +{ + size_t n; + char *result; + + if( strlen(name) > SIZE_MAX - strlen(suffix) - sizeof("lib") ) + return( NULL ); + + n = strlen(name) + strlen(suffix) + sizeof("lib"); + result = (char *)malloc( n ); + if( result != NULL ) + snprintf( result, n, "lib%s%s", name, suffix ); + + return( result ); + +} /* End of library_filename() */ + + +static int +process_libraries( const mcpu_ld_options *options, + cpu_asm_object **objects, size_t *object_count, + size_t *object_capacity, + mcpu_ld_library **libraries_out, size_t *count_out, + char *err, size_t es ) +{ + mcpu_ld_library *libraries = NULL; + size_t count = 0, i; + + *libraries_out = NULL; + *count_out = 0; + + if( options->library_count == 0 ) + return( 0 ); + + libraries = (mcpu_ld_library *)calloc( options->library_count, + sizeof(*libraries) ); + if( libraries == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < options->library_count; ++i ) + { + const char *name = options->libraries[i]; + char *shared_name = NULL; + char *archive_name = NULL; + const char *alternatives[2]; + size_t alternative_count = 0; + size_t selected_index = 0; + char *path = NULL; + + if( !options->force_static && + (options->mode == CPU_LINK_DYNAMIC_EXEC || + options->mode == CPU_LINK_SHARED) ) + { + shared_name = library_filename( name, ".so" ); + if( shared_name == NULL ) + { + snprintf( err, es, "library name is too long: %s", name ); + goto bad; + } + alternatives[alternative_count++] = shared_name; + } + + archive_name = library_filename( name, ".a" ); + if( archive_name == NULL ) + { + free( shared_name ); + snprintf( err, es, "library name is too long: %s", name ); + goto bad; + } + alternatives[alternative_count++] = archive_name; + + if( mcpu_tool_resolve_library_alternatives(&options->common, + alternatives, alternative_count, &path, &selected_index) != 0 ) + { + if( options->force_static || + options->mode == CPU_LINK_STATIC_EXEC || + options->mode == CPU_LINK_RELOCATABLE ) + snprintf( err, es, "cannot find -l%s (%s): %s", + name, archive_name, strerror(errno) ); + else + snprintf( err, es, "cannot find -l%s (%s or %s): %s", + name, shared_name, archive_name, strerror(errno) ); + free( shared_name ); + free( archive_name ); + goto bad; + } + + if( !options->force_static && + (options->mode == CPU_LINK_DYNAMIC_EXEC || + options->mode == CPU_LINK_SHARED) && selected_index == 0 ) + { + cpu_dyn_library_info info; + + memset( &info, 0, sizeof(info) ); + if( cpu_dyn_library_read(path, &info, err, es) != 0 ) + { + free( path ); + free( shared_name ); + free( archive_name ); + goto bad; + } + + if( find_soname(libraries, count, info.soname) >= 0 ) + { + free( path ); + cpu_dyn_library_free( &info ); + } + else + { + libraries[count].path = path; + libraries[count].info = info; + ++count; + } + } + else + { + mcpu_archive archive; + + mcpu_archive_init( &archive ); + if( mcpu_archive_read(path, &archive, err, es) != 0 ) + { + free( path ); + free( shared_name ); + free( archive_name ); + goto bad; + } + + if( extract_archive_members(path, &archive, + objects, object_count, object_capacity, + libraries, count, err, es) ) + { + mcpu_archive_free( &archive ); + free( path ); + free( shared_name ); + free( archive_name ); + goto bad; + } + + mcpu_archive_free( &archive ); + free( path ); + } + + free( shared_name ); + free( archive_name ); + } + + *libraries_out = libraries; + *count_out = count; + return( 0 ); + +bad: + free_libraries( libraries, count ); + return( -1 ); + +} /* End of process_libraries() */ + + +static int +validate_executable_imports( const cpu_asm_image *image, + const mcpu_ld_library *libraries, size_t count, + char *err, size_t es ) +{ + size_t i, j; + + for( i = 0; i < image->dyn_symbol_count; ++i ) + { + const cpu_dyn_symbol *need = &image->dyn_symbols[i]; + int found = 0; + + if( need->section != CPU_OBJ_SEC_UNDEF || + need->binding == CPU_OBJ_BIND_WEAK ) + continue; + + for( j = 0; j < count; ++j ) + { + const cpu_dyn_library_symbol *have = NULL; + + if( cpu_dyn_library_find_symbol(&libraries[j].info, need->name, + &have) == 0 ) + { + if( need->type != CPU_OBJ_TYPE_NOTYPE && + have->type != CPU_OBJ_TYPE_NOTYPE && + need->type != have->type ) + { + snprintf( err, es, + "symbol type mismatch for %s in %s", + need->name, libraries[j].path ); + return( -1 ); + } + found = 1; + break; + } + } + + if( !found ) + { + snprintf( err, es, "undefined symbol not provided by linked libraries: %s", + need->name ); + return( -1 ); + } + } + + return( 0 ); + +} /* End of validate_executable_imports() */ + + +static int +mcpu_ld_link( const mcpu_ld_options *options ) +{ + cpu_asm_object *objs = NULL; + cpu_asm_image im; + mcpu_ld_library *libraries = NULL; + size_t object_count; + size_t object_capacity; + size_t library_count = 0; + mcpu_hll_program_kind hll_kind = MCPU_HLL_NONE; + char err[512]; + size_t i; + int rc = 1; + + if( options == NULL ) + return( 1 ); + + object_count = options->input_count; + object_capacity = options->input_count; + objs = (cpu_asm_object *)calloc( object_capacity, sizeof(*objs) ); + if( objs == NULL ) + { + perror( "calloc" ); + return( 1 ); + } + + __mpu_init(); + memset( &im, 0, sizeof(im) ); + + for( i = 0; i < options->input_count; ++i ) + { + if( cpu_object_read(options->inputs[i], &objs[i], err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_mpu; + } + } + + if( options->mode == CPU_LINK_STATIC_EXEC || + options->mode == CPU_LINK_DYNAMIC_EXEC ) + { + if( classify_hll_program(objs, object_count, &hll_kind, + err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_mpu; + } + + if( hll_kind != MCPU_HLL_NONE && + inject_hll_crt(options, hll_kind, &objs, &object_count, + &object_capacity, err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_mpu; + } + } + + if( process_libraries(options, &objs, &object_count, &object_capacity, + &libraries, &library_count, + err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_mpu; + } + + if( options->mode == CPU_LINK_RELOCATABLE ) + { + cpu_asm_object merged; + + memset( &merged, 0, sizeof(merged) ); + if( cpu_link_relocatable(objs, object_count, &merged, err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_libraries; + } + + if( cpu_object_write(options->out_fname, &merged, err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + cpu_asm_object_free( &merged ); + goto cleanup_libraries; + } + + if( options->verbose ) + printf( "MELF ET_REL partial link: %zu symbols, %zu relocations -> %s\n", + merged.symbol_count, merged.reloc_count, options->out_fname ); + cpu_asm_object_free( &merged ); + rc = 0; + goto cleanup_libraries; + } + + if( cpu_link_objects_mode(objs, object_count, &im, + options->mode, err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_libraries; + } + + if( options->mode == CPU_LINK_DYNAMIC_EXEC && + options->library_count != 0 && + validate_executable_imports(&im, libraries, library_count, + err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_image; + } + + if( options->mode == CPU_LINK_STATIC_EXEC ) + { + if( cpu_exec_write(options->out_fname, &im, &options->exec, + err, sizeof(err)) ) + { + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_image; + } + + if( options->verbose ) + { + printf( "MELF ABI1 ET_EXEC: PROGRAM=%zu DATA=%zu STACK=%llu", + im.code_size, im.data_size, + (unsigned long long)options->exec.stack_size ); + if( options->exec.have_heap ) + printf( " HEAP=%llu/%llu", + (unsigned long long)options->exec.heap_size, + (unsigned long long)options->exec.heap_limit ); + printf( " -> %s\n", options->out_fname ); + } + } + else + { + cpu_dyn_metadata metadata; + const char **needed = NULL; + + memset( &metadata, 0, sizeof( metadata ) ); + metadata.soname = options->soname; + if( library_count != 0 ) + { + needed = (const char **)calloc( library_count, sizeof(*needed) ); + if( needed == NULL ) + { + fprintf( stderr, "%s: out of memory\n", options->common.progname ); + goto cleanup_image; + } + for( i = 0; i < library_count; ++i ) + needed[i] = libraries[i].info.soname; + metadata.needed = needed; + metadata.needed_count = library_count; + } + + if( cpu_dyn_write_abi3(options->out_fname, &im, + options->mode == CPU_LINK_SHARED ? NULL : &options->exec, + options->mode == CPU_LINK_SHARED, + &metadata, err, sizeof(err)) ) + { + free( needed ); + fprintf( stderr, "%s: %s\n", options->common.progname, err ); + goto cleanup_image; + } + free( needed ); + + if( options->verbose ) + { + if( options->mode == CPU_LINK_SHARED ) + printf( "MELF ABI3 ET_DYN: PROGRAM=%zu DATA=%zu DYNSYM=%zu RELA=%zu NEEDED=%zu -> %s\n", + im.code_size, im.data_size, im.dyn_symbol_count, + im.dyn_reloc_count, library_count, options->out_fname ); + else + { + printf( "MELF ABI3 ET_EXEC: PROGRAM=%zu DATA=%zu DYNSYM=%zu RELA=%zu NEEDED=%zu STACK=%llu", + im.code_size, im.data_size, im.dyn_symbol_count, + im.dyn_reloc_count, library_count, + (unsigned long long)options->exec.stack_size ); + if( options->exec.have_heap ) + printf( " HEAP=%llu/%llu", + (unsigned long long)options->exec.heap_size, + (unsigned long long)options->exec.heap_limit ); + printf( " -> %s\n", options->out_fname ); + } + } + } + + rc = 0; + +cleanup_image: + cpu_asm_image_free( &im ); + +cleanup_libraries: + free_libraries( libraries, library_count ); + +cleanup_mpu: + for( i = 0; i < object_count; ++i ) + cpu_asm_object_free( &objs[i] ); + free( objs ); + __mpu_free_context(); + + return( rc ); + +} /* End of mcpu_ld_link() */ + + +int +main( int argc, char **argv ) +{ + mcpu_ld_options options; + int rc = 1; + + mcpu_ld_options_init( &options, argv[0] ); + + if( mcpu_ld_handle_options(&options, argc, argv) != 0 ) + goto done; + + if( options.common.help ) + { + mcpu_ld_usage( &options ); + rc = 0; + goto done; + } + + if( options.common.version_action != MCPU_VERSION_NONE ) + { + if( mcpu_version_print(options.common.progname, + options.common.version_action) != 0 ) + goto done; + rc = 0; + goto done; + } + + if( mcpu_tool_options_prepare(&options.common, argv[0]) != 0 ) + goto done; + + if( mcpu_tool_has_information_action(&options.common) ) + { + if( options.common.dump_config && + mcpu_tool_dump_config(&options.common, stdout) != 0 ) + goto done; + if( options.common.print_search_dirs && + mcpu_tool_print_search_dirs(&options.common, stdout) != 0 ) + goto done; + rc = 0; + goto done; + } + + rc = mcpu_ld_link( &options ); + +done: + mcpu_ld_options_free( &options ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-library-search.c b/utils/mcpu-library-search.c new file mode 100644 index 0000000..dc877b9 --- /dev/null +++ b/utils/mcpu-library-search.c @@ -0,0 +1,307 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-library-search.h" + + +static char * +path_join( const char *directory, const char *name ) +{ + size_t directory_length; + size_t name_length; + int need_slash; + char *result; + + if( directory == NULL || name == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + directory_length = strlen( directory ); + name_length = strlen( name ); + need_slash = directory_length != 0 && directory[directory_length - 1] != '/'; + + if( directory_length > SIZE_MAX - name_length - (size_t)need_slash - 1 ) + { + errno = EOVERFLOW; + return( NULL ); + } + + result = (char *)malloc( directory_length + name_length + + (size_t)need_slash + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, directory, directory_length ); + if( need_slash ) + result[directory_length++] = '/'; + memcpy( result + directory_length, name, name_length ); + result[directory_length + name_length] = 0; + return( result ); + +} /* End of path_join() */ + + +static int +regular_file( const char *path ) +{ + struct stat st; + + if( stat(path, &st) != 0 ) + return( 0 ); + + return( S_ISREG(st.st_mode) ); + +} /* End of regular_file() */ + + +void +mcpu_library_search_init( mcpu_library_search *search ) +{ + if( search == NULL ) + return; + + search->directory = NULL; + search->count = 0; + search->capacity = 0; + +} /* End of mcpu_library_search_init() */ + + +void +mcpu_library_search_free( mcpu_library_search *search ) +{ + size_t i; + + if( search == NULL ) + return; + + for( i = 0; i < search->count; ++i ) + free( search->directory[i] ); + free( search->directory ); + mcpu_library_search_init( search ); + +} /* End of mcpu_library_search_free() */ + + +int +mcpu_library_search_add_directory( mcpu_library_search *search, + const char *directory ) +{ + char **p; + char *copy; + size_t capacity; + + if( search == NULL || directory == NULL || *directory == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( search->count == search->capacity ) + { + capacity = search->capacity ? search->capacity * 2U : 4U; + if( capacity < search->capacity || capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( search->directory, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + search->directory = p; + search->capacity = capacity; + } + + copy = strdup( directory ); + if( copy == NULL ) + return( -1 ); + + search->directory[search->count++] = copy; + return( 0 ); + +} /* End of mcpu_library_search_add_directory() */ + + +int +mcpu_library_search_add_list( mcpu_library_search *search, const char *list ) +{ + const char *begin; + + if( search == NULL || list == NULL || *list == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *directory; + int rc; + + if( length == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + directory = (char *)malloc( length + 1 ); + if( directory == NULL ) + return( -1 ); + + memcpy( directory, begin, length ); + directory[length] = 0; + rc = mcpu_library_search_add_directory( search, directory ); + free( directory ); + if( rc != 0 ) + return( -1 ); + + if( colon == NULL ) + break; + begin = colon + 1; + } + + return( 0 ); + +} /* End of mcpu_library_search_add_list() */ + + +static int +try_directory( const char *directory, const char *name, char **resolved ) +{ + char *candidate; + + if( directory == NULL || *directory == 0 ) + return( 0 ); + + candidate = path_join( directory, name ); + if( candidate == NULL ) + return( -1 ); + + if( regular_file(candidate) ) + { + *resolved = candidate; + return( 1 ); + } + + free( candidate ); + return( 0 ); + +} /* End of try_directory() */ + + +int +mcpu_library_search_resolve( const mcpu_library_search *search, + const char *system_directory, + const char *name, char **resolved ) +{ + size_t i; + int rc; + + if( search == NULL || name == NULL || *name == 0 || resolved == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + + if( strchr(name, '/') != NULL ) + { + *resolved = strdup( name ); + return( *resolved == NULL ? -1 : 0 ); + } + + for( i = 0; i < search->count; ++i ) + { + rc = try_directory( search->directory[i], name, resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + return( 0 ); + } + + rc = try_directory( system_directory, name, resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + return( 0 ); + + errno = ENOENT; + return( -1 ); + +} /* End of mcpu_library_search_resolve() */ + + +int +mcpu_library_search_resolve_alternatives( + const mcpu_library_search *search, + const char *system_directory, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ) +{ + size_t i, j; + int rc; + + if( search == NULL || names == NULL || name_count == 0 || + resolved == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( j = 0; j < name_count; ++j ) + if( names[j] == NULL || names[j][0] == 0 || strchr(names[j], '/') != NULL ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + if( selected_index ) + *selected_index = 0; + + for( i = 0; i < search->count; ++i ) + for( j = 0; j < name_count; ++j ) + { + rc = try_directory( search->directory[i], names[j], resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + { + if( selected_index ) + *selected_index = j; + return( 0 ); + } + } + + for( j = 0; j < name_count; ++j ) + { + rc = try_directory( system_directory, names[j], resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + { + if( selected_index ) + *selected_index = j; + return( 0 ); + } + } + + errno = ENOENT; + return( -1 ); + +} /* End of mcpu_library_search_resolve_alternatives() */ diff --git a/utils/mcpu-library-search.h b/utils/mcpu-library-search.h new file mode 100644 index 0000000..66331a6 --- /dev/null +++ b/utils/mcpu-library-search.h @@ -0,0 +1,29 @@ +#ifndef __MCPU_LIBRARY_SEARCH_H__ +#define __MCPU_LIBRARY_SEARCH_H__ 1 + +#include <stddef.h> + +typedef struct mcpu_library_search mcpu_library_search; +struct mcpu_library_search +{ + char **directory; + size_t count; + size_t capacity; +}; + +void mcpu_library_search_init( mcpu_library_search *search ); +void mcpu_library_search_free( mcpu_library_search *search ); +int mcpu_library_search_add_directory( mcpu_library_search *search, + const char *directory ); +int mcpu_library_search_add_list( mcpu_library_search *search, + const char *list ); +int mcpu_library_search_resolve( const mcpu_library_search *search, + const char *system_directory, + const char *name, char **resolved ); +int mcpu_library_search_resolve_alternatives( + const mcpu_library_search *search, + const char *system_directory, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ); + +#endif /* __MCPU_LIBRARY_SEARCH_H__ */ diff --git a/utils/mcpu-link.c b/utils/mcpu-link.c new file mode 100644 index 0000000..b2a9abe --- /dev/null +++ b/utils/mcpu-link.c @@ -0,0 +1,1647 @@ +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> + +#include "mcpu-link.h" +#include "mcpu-melf.h" + +typedef struct lbuf +{ + __mpu_UNIT_t *p; + size_t n; + size_t cap; +} lbuf; + +typedef struct gsym +{ + const char *name; + size_t obj; + size_t sym; + unsigned binding; +} gsym; + +enum link_section_index +{ + LSEC_INIT = 0, + LSEC_TEXT, + LSEC_FINI, + LSEC_DATA, + LSEC_CONTROL, + LSEC_INPUT, + LSEC_OUTPUT, + LSEC_TRACE, + LSEC_BSS, + LSEC_COUNT +}; + +static int +grow( void **p, size_t *cap, size_t need, size_t unit ) +{ + size_t c = *cap ? *cap : 64; + void *q; + + while( c < need ) + { + if( c > SIZE_MAX / 2 ) + return( -1 ); + c *= 2; + } + + q = realloc( *p, c *unit ); + if( !q ) + return( -1 ); + + *p = q; + *cap = c; + return( 0 ); +} + +static int +put( lbuf *b, const void *p, size_t n ) +{ + if( n > SIZE_MAX - b->n || grow( (void **)&b->p, &b->cap, b->n + n, 1 ) ) + return( -1 ); + if( n ) + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +padalign( lbuf *b, __mpu_uint64_t a ) +{ + if( !a || ( a & ( a - 1 ) ) != 0 ) + return( -1 ); + + while( (__mpu_uint64_t)b->n % a ) + { + __mpu_UNIT_t z = 0; + if( put( b, &z, 1 ) ) + return( -1 ); + } + return( 0 ); +} + +static int +align_u64( __mpu_uint64_t v, __mpu_uint64_t a, __mpu_uint64_t *out ) +{ + __mpu_uint64_t rem, add; + + if( !a || ( a & ( a - 1 ) ) != 0 ) + return( -1 ); + rem = v % a; + add = rem ? a - rem : 0; + if( v > UINT64_MAX - add ) + return( -1 ); + *out = v + add; + return( 0 ); +} + +static int +sidx( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( LSEC_INIT ); + case CPU_OBJ_SEC_TEXT: return( LSEC_TEXT ); + case CPU_OBJ_SEC_FINI: return( LSEC_FINI ); + case CPU_OBJ_SEC_DATA: return( LSEC_DATA ); + case CPU_OBJ_SEC_CONTROL: return( LSEC_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( LSEC_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( LSEC_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( LSEC_TRACE ); + case CPU_OBJ_SEC_BSS: return( LSEC_BSS ); + default: return( -1 ); + } +} + +static const __mpu_UNIT_t * +objdata( const cpu_asm_object *o, int i, size_t *n, + __mpu_uint64_t *a, int *present) +{ + switch( i ) + { + case LSEC_INIT: + *n = o->init_size; + *a = o->init_align; + *present = o->has_init; + return( o->init ); + case LSEC_TEXT: + *n = o->text_size; + *a = o->text_align; + *present = o->has_text; + return( o->text ); + case LSEC_FINI: + *n = o->fini_size; + *a = o->fini_align; + *present = o->has_fini; + return( o->fini ); + case LSEC_DATA: + *n = o->data_size; + *a = o->data_align; + *present = o->has_data || o->data_size; + return( o->data ); + case LSEC_CONTROL: + *n = o->control_size; + *a = o->control_align; + *present = o->has_control || o->control_size; + return( o->control ); + case LSEC_INPUT: + *n = o->input_size; + *a = o->input_align; + *present = o->has_input || o->input_size; + return( o->input ); + case LSEC_OUTPUT: + *n = o->output_size; + *a = o->output_align; + *present = o->has_output || o->output_size; + return( o->output ); + case LSEC_TRACE: + *n = o->trace_size; + *a = o->trace_align; + *present = o->has_trace || o->trace_size; + return( o->trace ); + default: + *n = 0; + *a = 1; + *present = 0; + return( 0 ); + } +} + +static int +find_global( const gsym *g, size_t ng, const char *n, size_t *idx ) +{ + size_t i; + for( i = 0; i < ng; ++i ) + if( !strcmp( g[i].name, n ) ) + { + if( idx ) + *idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +add_global( gsym **g, size_t *ng, size_t *cap, + const cpu_asm_object *objs, size_t oi, size_t si, + char *err, size_t es) +{ + const cpu_asm_symbol *s = &objs[oi].symbols[si]; + size_t x; + + if( !find_global( *g, *ng, s->name, &x ) ) + { + gsym *old = &(*g)[x]; + const cpu_asm_symbol *os = &objs[old->obj].symbols[old->sym]; + + if( os->binding == CPU_OBJ_BIND_GLOBAL && s->binding == CPU_OBJ_BIND_GLOBAL ) + { + snprintf( err, es, "multiple definition of global symbol: %s", s->name ); + return( -1 ); + } + if( os->binding == CPU_OBJ_BIND_WEAK && s->binding == CPU_OBJ_BIND_GLOBAL ) + { + old->obj = oi; + old->sym = si; + old->binding = s->binding; + } + /* A strong GLOBAL already beats WEAK; first WEAK wins deterministically. */ + return( 0 ); + } + + if( grow( (void **)g, cap, *ng + 1, sizeof( **g ) ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + ( *g )[*ng] = ( gsym ) + { + s->name, oi, si, s->binding + }; + ++*ng; + return( 0 ); +} + +typedef enum cpu_hll_link_kind +{ + CPU_HLL_LINK_NONE = 0, + CPU_HLL_LINK_PROGRAM, + CPU_HLL_LINK_MODEL +} cpu_hll_link_kind; + + +static int +is_lifecycle_name( const char *name ) +{ + return name && ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || + !strcmp( name, "_fini" )); +} + + +static cpu_hll_link_kind +hll_link_kind( const cpu_asm_object *objs, size_t nobjs ) +{ + size_t oi, si; + int program = 0, model = 0; + + for( oi = 0; oi < nobjs; ++oi ) + for( si = 0; si < objs[oi].symbol_count; ++si ) + if( objs[oi].symbols[si].section != CPU_OBJ_SEC_UNDEF ) + { + if( !strcmp(objs[oi].symbols[si].name, + "__mcpu_crt_hll_program") ) + program = 1; + else if( !strcmp(objs[oi].symbols[si].name, + "__mcpu_crt_hll_model") ) + model = 1; + } + + if( program && !model ) + return( CPU_HLL_LINK_PROGRAM ); + if( model && !program ) + return( CPU_HLL_LINK_MODEL ); + return( CPU_HLL_LINK_NONE ); +} + + +static int +is_runtime_hook_name( const char *name ) +{ + if( name == NULL ) + return( 0 ); + + return( !strcmp(name, "__mcpu_libc_init") || + !strcmp(name, "__mcpu_init_array") || + !strcmp(name, "__mcpu_fini_array") || + !strcmp(name, "__mcpu_libc_fini") ); +} + + +static int +is_nonpreemptible_name( const char *name, cpu_hll_link_kind kind ) +{ + if( is_lifecycle_name(name) ) + return( 1 ); + + if( name != NULL && !strncmp(name, "__mcpu_crt_", 11) ) + return( 1 ); + + if( kind == CPU_HLL_LINK_PROGRAM ) + return( name && (!strcmp(name, "main") || !strcmp(name, "fini")) ); + + if( kind == CPU_HLL_LINK_MODEL ) + return( name && (!strcmp(name, "init") || !strcmp(name, "life") || + !strcmp(name, "fini")) ); + + return( 0 ); +} + +static int +find_dyn_symbol( const cpu_asm_image *im, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < im->dyn_symbol_count; ++i ) + if( !strcmp( im->dyn_symbols[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +add_dyn_symbol( cpu_asm_image *im, const char *name, unsigned section, + unsigned binding, unsigned type, __mpu_uint64_t value, + __mpu_uint64_t size, size_t *idx, char *err, size_t es) +{ + cpu_dyn_symbol *q; + size_t i; + + if( !find_dyn_symbol( im, name, &i ) ) + { + cpu_dyn_symbol *d = &im->dyn_symbols[i]; + if( d->section == CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF ) + { + d->section = section; + d->value = value; + d->size = size; + d->type = type; + d->binding = binding; + } + else if( d->section == CPU_OBJ_SEC_UNDEF && binding == CPU_OBJ_BIND_GLOBAL ) + d->binding = CPU_OBJ_BIND_GLOBAL; + if( idx ) * idx = i; + return( 0 ); + } + + q = (cpu_dyn_symbol *)realloc( im->dyn_symbols, + (im->dyn_symbol_count + 1) * sizeof(*q)); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + im->dyn_symbols = q; + memset( &im->dyn_symbols[im->dyn_symbol_count], 0, + sizeof(im->dyn_symbols[im->dyn_symbol_count])); + im->dyn_symbols[im->dyn_symbol_count].name = strdup( name ); + if( !im->dyn_symbols[im->dyn_symbol_count].name ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + im->dyn_symbols[im->dyn_symbol_count].section = section; + im->dyn_symbols[im->dyn_symbol_count].binding = binding; + im->dyn_symbols[im->dyn_symbol_count].type = type; + im->dyn_symbols[im->dyn_symbol_count].value = value; + im->dyn_symbols[im->dyn_symbol_count].size = size; + if( idx ) * idx = im->dyn_symbol_count; + ++im->dyn_symbol_count; + return( 0 ); +} + +static int +add_dyn_reloc( cpu_asm_image *im, unsigned section, unsigned type, + size_t symbol_index, __mpu_uint64_t offset, + __mpu_int64_t addend, char *err, size_t es) +{ + cpu_dyn_reloc *q = (cpu_dyn_reloc *)realloc( + im->dyn_relocs, (im->dyn_reloc_count + 1) * sizeof(*q)); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + im->dyn_relocs = q; + im->dyn_relocs[im->dyn_reloc_count] = + ( cpu_dyn_reloc ) + { + section, type, symbol_index, offset, addend + }; + ++im->dyn_reloc_count; + return( 0 ); +} + +static int +add_signed( __mpu_uint64_t b, __mpu_int64_t a, __mpu_uint64_t *out ) +{ + if( a >= 0 ) + { + __mpu_uint64_t u = (__mpu_uint64_t)a; + if( b > UINT64_MAX - u ) + return( -1 ); + *out = b + u; + } + else + { + __mpu_uint64_t u = (__mpu_uint64_t)(-(a + 1)) + 1; + if( b < u ) + return( -1 ); + *out = b - u; + } + return( 0 ); +} + +static int +linker_end_pointer( __mpu_int64_t addend, __mpu_uint64_t *ptr ) +{ + __mpu_uint64_t off; + + if( add_signed( CPU_HEAP_BEGIN, addend, &off ) || + off > CPU_PTR_OFFSET_MASK || + __cpu_ptr_make( ptr, CPU_PTR_SEG_HEAP, off )) + return( -1 ); + + return( 0 ); +} + +static int +strong_lifecycle( const cpu_asm_symbol *s, const char *n, char *err, size_t es ) +{ + if( s->binding != CPU_OBJ_BIND_GLOBAL || s->type != CPU_OBJ_TYPE_FUNC ) + { + snprintf( err, es, "lifecycle symbol %s must be strong GLOBAL/FUNC", n ); + return( -1 ); + } + return( 0 ); +} + +static int +source_field( cpu_asm_object *objs, size_t oi, const __mpu_uint64_t *bases, + int srcidx, __mpu_uint64_t rel_off, + lbuf *program, lbuf space[5], + __mpu_UNIT_t **dst, size_t *dsz, __mpu_uint64_t *field, + char *err, size_t es) +{ + (void)objs; + if( srcidx < 0 || srcidx == LSEC_BSS ) + { + snprintf( err, es, "bad relocation source section" ); + return( -1 ); + } + + if( bases[oi *LSEC_COUNT + (size_t)srcidx] > UINT64_MAX - rel_off ) + { + snprintf( err, es, "relocation field address overflow" ); + return( -1 ); + } + *field = bases[oi * LSEC_COUNT + (size_t)srcidx] + rel_off; + + if( srcidx < 3 ) + { + *dst = program->p; + *dsz = program->n; + } + else + { + int spidx; + switch( srcidx ) + { + case LSEC_DATA: spidx = 0; + break; + case LSEC_CONTROL: spidx = 1; + break; + case LSEC_INPUT: spidx = 2; + break; + case LSEC_OUTPUT: spidx = 3; + break; + case LSEC_TRACE: spidx = 4; + break; + default: + snprintf( err, es, "bad relocation source section" ); + return( -1 ); + } + *dst = space[spidx].p; + *dsz = space[spidx].n; + } + + if( *field > *dsz || 8 > *dsz - (size_t)*field ) + { + snprintf( err, es, "relocation field outside section" ); + return( -1 ); + } + return( 0 ); +} + +int +cpu_link_objects_mode( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *im, + cpu_link_mode mode, char *err, size_t es) +{ + lbuf program = {0}; + lbuf space[5] = {{0}}; /* DATA, CONTROL, INPUT, OUTPUT, TRACE file images */ + __mpu_uint64_t *bases = 0; + __mpu_uint64_t sec_start[LSEC_COUNT] = {0}; + __mpu_uint64_t sec_end[LSEC_COUNT] = {0}; + __mpu_uint64_t sec_align[LSEC_COUNT] = + { + 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + int sec_present[LSEC_COUNT] = {0}; + __mpu_uint64_t data_mem_size = 0; + gsym *globals = 0; + size_t ng = 0, cg = 0, oi, si, ri; + cpu_hll_link_kind hll_kind; + int rc = -1; + + if( !objs || !nobjs || !im ) + return( -1 ); + memset( im, 0, sizeof( *im ) ); + + if( mode != CPU_LINK_STATIC_EXEC && mode != CPU_LINK_DYNAMIC_EXEC && + mode != CPU_LINK_SHARED) + { + snprintf( err, es, "invalid link mode" ); + goto done; + } + + hll_kind = hll_link_kind( objs, nobjs ); + + if( mode == CPU_LINK_SHARED ) + { + for( oi = 0; oi < nobjs; ++oi ) + { + size_t sj; + if( objs[oi].has_init || objs[oi].init_size || + objs[oi].has_fini || objs[oi].fini_size || + objs[oi].has_control || objs[oi].control_size || + objs[oi].has_input || objs[oi].input_size || + objs[oi].has_output || objs[oi].output_size || + objs[oi].has_trace || objs[oi].trace_size) + { + snprintf( err, es, + "shared object may contain only .text/.data/.bss allocated sections"); + goto done; + } + for( sj = 0; sj < objs[oi].symbol_count; ++sj ) + if( objs[oi].symbols[sj].section != CPU_OBJ_SEC_UNDEF && + is_lifecycle_name( objs[oi].symbols[sj].name )) + { + snprintf( err, es, "shared object must not define lifecycle symbol %s", + objs[oi].symbols[sj].name); + goto done; + } + } + } + + if( nobjs > SIZE_MAX / LSEC_COUNT ) + { + snprintf( err, es, "too many input objects" ); + goto done; + } + bases = (__mpu_uint64_t *)calloc( nobjs *LSEC_COUNT, sizeof( *bases ) ); + if( !bases ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + /* DATA owns one NULL guard if either .data or .bss makes DATA exist. */ + { + int data_needed = 0; + for( oi = 0; oi < nobjs; ++oi ) + if( objs[oi].has_data || objs[oi].data_size || + objs[oi].has_bss || objs[oi].bss_size) + { + data_needed = 1; + break; + } + + if( data_needed && mode != CPU_LINK_SHARED ) + while( space[0].n < CPU_DATA_NULL_GUARD_SIZE ) + { + __mpu_UNIT_t z = 0; + if( put( &space[0], &z, 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + } + + /* PROGRAM: all .init contributions, then all .text, then all .fini. */ + for( si = LSEC_INIT; si <= LSEC_FINI; ++si ) + { + int first = 1; + for( oi = 0; oi < nobjs; ++oi ) + { + const __mpu_UNIT_t *p; + size_t n; + __mpu_uint64_t a; + int present; + + p = objdata( &objs[oi], (int)si, &n, &a, &present ); + if( !present ) + continue; + if( !a ) + a = 1; + if( !cpu_melf_is_pow2( a ) ) + { + snprintf( err, es, "invalid PROGRAM contribution alignment" ); + goto done; + } + sec_present[si] = 1; + if( a > sec_align[si] ) + sec_align[si] = a; + if( padalign( &program, a ) ) + { + snprintf( err, es, "cannot align PROGRAM contribution" ); + goto done; + } + if( first ) + { + sec_start[si] = program.n; + first = 0; + } + bases[oi * LSEC_COUNT + si] = program.n; + if( put( &program, p, n ) ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + sec_end[si] = program.n; + } + + /* File-backed data-like spaces. */ + for( si = LSEC_DATA; si <= LSEC_TRACE; ++si ) + { + lbuf *b; + int first = 1; + + if( si == LSEC_DATA ) b = &space[0]; + else if( si == LSEC_CONTROL ) b = &space[1]; + else if( si == LSEC_INPUT ) b = &space[2]; + else if( si == LSEC_OUTPUT ) b = &space[3]; + else b = &space[4]; + + for( oi = 0; oi < nobjs; ++oi ) + { + const __mpu_UNIT_t *p; + size_t n; + __mpu_uint64_t a; + int present; + + p = objdata( &objs[oi], (int)si, &n, &a, &present ); + if( !present ) + continue; + if( !a ) + a = 1; + if( !cpu_melf_is_pow2( a ) ) + { + snprintf( err, es, "invalid section contribution alignment" ); + goto done; + } + sec_present[si] = 1; + if( a > sec_align[si] ) + sec_align[si] = a; + if( padalign( b, a ) ) + { + snprintf( err, es, "cannot align output section" ); + goto done; + } + if( first ) + { + sec_start[si] = b->n; + first = 0; + } + bases[oi * LSEC_COUNT + si] = b->n; + if( put( b, p, n ) ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + sec_end[si] = b->n; + } + + /* .bss follows .data inside DATA but consumes no executable file bytes. */ + data_mem_size = space[0].n; + { + int first = 1; + for( oi = 0; oi < nobjs; ++oi ) + { + __mpu_uint64_t a = objs[oi].bss_align ? objs[oi].bss_align : 1; + __mpu_uint64_t aligned; + int present = objs[oi].has_bss || objs[oi].bss_size; + + if( !present ) + continue; + if( !cpu_melf_is_pow2( a ) || align_u64( data_mem_size, a, &aligned ) ) + { + snprintf( err, es, "cannot align .bss contribution" ); + goto done; + } + data_mem_size = aligned; + sec_present[LSEC_BSS] = 1; + if( a > sec_align[LSEC_BSS] ) + sec_align[LSEC_BSS] = a; + if( first ) + { + sec_start[LSEC_BSS] = data_mem_size; + first = 0; + } + bases[oi * LSEC_COUNT + LSEC_BSS] = data_mem_size; + if( (__mpu_uint64_t)objs[oi].bss_size > UINT64_MAX - data_mem_size ) + { + snprintf( err, es, ".bss layout overflow" ); + goto done; + } + data_mem_size += (__mpu_uint64_t)objs[oi].bss_size; + } + sec_end[LSEC_BSS] = data_mem_size; + } + + if( data_mem_size > CPU_PTR_OFFSET_MASK || data_mem_size > SIZE_MAX ) + { + snprintf( err, es, "final DATA space exceeds supported range" ); + goto done; + } + + /* _end is defined by mcpu-ld as HEAP:CPU_HEAP_BEGIN. */ + for( oi = 0; oi < nobjs; ++oi ) + for( si = 0; si < objs[oi].symbol_count; ++si ) + if( objs[oi].symbols[si].section != CPU_OBJ_SEC_UNDEF && + !strcmp( objs[oi].symbols[si].name, "_end" )) + { + snprintf( err, es, "_end is a reserved linker-defined symbol" ); + goto done; + } + + /* Collect defined non-local symbols. */ + for( oi = 0; oi < nobjs; ++oi ) + for( si = 0; si < objs[oi].symbol_count; ++si ) + { + cpu_asm_symbol *s = &objs[oi].symbols[si]; + if( s->section != CPU_OBJ_SEC_UNDEF && + (s->binding == CPU_OBJ_BIND_GLOBAL || s->binding == CPU_OBJ_BIND_WEAK)) + if( add_global( &globals, &ng, &cg, objs, oi, si, err, es ) ) + goto done; + } + + /* + * ET_DYN local relocations need startup rebasing too. Represent their + * targets by LOCAL dynamic symbols so the runtime linker can add the + * module segment base without making them preemptible. + */ + if( mode == CPU_LINK_SHARED ) + for( oi = 0; oi < nobjs; ++oi ) + for( ri = 0; ri < objs[oi].reloc_count; ++ri ) + { + cpu_asm_reloc *r = &objs[oi].relocs[ri]; + cpu_asm_symbol *s; + int ix; + __mpu_uint64_t v; + char lname[64]; + + if( r->symbol_index >= objs[oi].symbol_count ) + { + snprintf( err, es, "bad relocation symbol index" ); + goto done; + } + s = &objs[oi].symbols[r->symbol_index]; + if( s->binding != CPU_OBJ_BIND_LOCAL || s->section == CPU_OBJ_SEC_UNDEF ) + continue; + + ix = sidx( s->section ); + if( ix < 0 || bases[oi *LSEC_COUNT + (size_t)ix] < sec_start[ix] || + s->value > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix]) + { + snprintf( err, es, "bad local dynamic symbol placement: %s", s->name ); + goto done; + } + v = bases[oi * LSEC_COUNT + (size_t)ix] + s->value - sec_start[ix]; + snprintf( lname, sizeof( lname ), ".L$%zu$%zu", oi, r->symbol_index ); + if( add_dyn_symbol( im, lname, s->section, CPU_OBJ_BIND_LOCAL, s->type, + v, s->size, 0, err, es)) + goto done; + } + + /* Build ABI-v2/ABI-v3 runtime symbol definitions before retaining relocations. */ + if( mode != CPU_LINK_STATIC_EXEC ) + for( si = 0; si < ng; ++si ) + { + cpu_asm_symbol *s = &objs[globals[si].obj].symbols[globals[si].sym]; + int ix = sidx( s->section ); + __mpu_uint64_t v; + if( is_nonpreemptible_name(s->name, hll_kind) ) + continue; + if( ix < 0 || bases[globals[si].obj *LSEC_COUNT + (size_t)ix] < sec_start[ix] || + s->value > UINT64_MAX - bases[globals[si].obj * LSEC_COUNT + (size_t)ix]) + { + snprintf( err, es, "bad dynamic symbol placement: %s", s->name ); + goto done; + } + v = bases[globals[si].obj * LSEC_COUNT + (size_t)ix] + s->value - sec_start[ix]; + if( add_dyn_symbol( im, s->name, s->section, s->binding, s->type, + v, s->size, 0, err, es)) + goto done; + } + + /* Apply static relocations, or retain preemptible ABI-v2/ABI-v3 relocations. */ + for( oi = 0; oi < nobjs; ++oi ) + for( ri = 0; ri < objs[oi].reloc_count; ++ri ) + { + cpu_asm_reloc *r = &objs[oi].relocs[ri]; + cpu_asm_symbol *orig, *s; + size_t toi = oi, tsi = r->symbol_index, gi; + int tidx, srcidx; + unsigned tseg; + __mpu_uint64_t sval, final, field, ptr; + __mpu_UNIT_t *dst; + size_t dsz; + + if( tsi >= objs[oi].symbol_count ) + { + snprintf( err, es, "bad relocation symbol index" ); + goto done; + } + orig = &objs[oi].symbols[tsi]; + s = orig; + srcidx = sidx( r->section ); + if( srcidx < 0 || srcidx == LSEC_BSS ) + { + snprintf( err, es, "bad relocation source section" ); + goto done; + } + + /* _end is an executable-only, non-preemptible linker-defined symbol. */ + if( !strcmp( orig->name, "_end" ) ) + { + if( mode == CPU_LINK_SHARED ) + { + snprintf( err, es, "_end is not defined for ET_DYN shared objects" ); + goto done; + } + if( r->type != CPU_RELOC_PTR64_CODE && r->type != CPU_RELOC_PTR64_DATA ) + { + snprintf( err, es, "_end requires a PTR64 relocation" ); + goto done; + } + if( linker_end_pointer( r->addend, &ptr ) ) + { + snprintf( err, es, "_end relocation overflow" ); + goto done; + } + if( source_field( objs, oi, bases, srcidx, r->offset, + &program, space, &dst, &dsz, &field, err, es)) + goto done; + if( r->type == CPU_RELOC_PTR64_CODE ) + __cpu_code_put_u64( dst + (size_t)field, ptr ); + else + __cpu_data_put_u64( dst + (size_t)field, ptr ); + continue; + } + + /* + * A local ET_DYN reference is not preemptible, but its section-relative + * value still needs the shared object's runtime segment base. + */ + if( mode == CPU_LINK_SHARED && orig->binding == CPU_OBJ_BIND_LOCAL && + orig->section != CPU_OBJ_SEC_UNDEF) + { + size_t di; + __mpu_uint64_t roff; + char lname[64]; + + if( source_field( objs, oi, bases, srcidx, r->offset, + &program, space, &dst, &dsz, &field, err, es)) + goto done; + if( field < sec_start[srcidx] ) + { + snprintf( err, es, "local dynamic relocation precedes output section" ); + goto done; + } + roff = field - sec_start[srcidx]; + snprintf( lname, sizeof( lname ), ".L$%zu$%zu", oi, r->symbol_index ); + if( find_dyn_symbol( im, lname, &di ) ) + { + snprintf( err, es, "missing local dynamic symbol: %s", orig->name ); + goto done; + } + if( add_dyn_reloc( im, r->section, r->type, di, roff, + r->addend, err, es)) + goto done; + continue; + } + + /* + * ABI-v2/ABI-v3 rule: every reference through a DEFAULT GLOBAL/WEAK name is + * preemptible, even when this output itself defines the name. The + * startup linker in mcpu-run chooses the final definition. Lifecycle + * entry symbols are deliberately non-preemptible. + */ + if( mode != CPU_LINK_STATIC_EXEC && + (orig->binding == CPU_OBJ_BIND_GLOBAL || orig->binding == CPU_OBJ_BIND_WEAK) && + !is_nonpreemptible_name(orig->name, hll_kind) ) + { + size_t di; + __mpu_uint64_t roff; + + if( source_field( objs, oi, bases, srcidx, r->offset, + &program, space, &dst, &dsz, &field, err, es)) + goto done; + if( field < sec_start[srcidx] ) + { + snprintf( err, es, "dynamic relocation precedes output section" ); + goto done; + } + roff = field - sec_start[srcidx]; + + if( !find_global( globals, ng, orig->name, &gi ) ) + { + cpu_asm_symbol *def = &objs[globals[gi].obj].symbols[globals[gi].sym]; + int dix = sidx( def->section ); + __mpu_uint64_t dv; + if( dix < 0 || bases[globals[gi].obj *LSEC_COUNT + (size_t)dix] < sec_start[dix] || + def->value > UINT64_MAX - bases[globals[gi].obj * LSEC_COUNT + (size_t)dix]) + { + snprintf( err, es, "bad dynamic symbol placement: %s", def->name ); + goto done; + } + dv = bases[globals[gi].obj * LSEC_COUNT + (size_t)dix] + def->value - sec_start[dix]; + if( add_dyn_symbol( im, def->name, def->section, def->binding, def->type, + dv, def->size, &di, err, es)) + goto done; + } + else if( add_dyn_symbol( im, orig->name, CPU_OBJ_SEC_UNDEF, + orig->binding, orig->type, 0, 0, + &di, err, es)) + goto done; + + if( add_dyn_reloc( im, r->section, r->type, di, roff, + r->addend, err, es)) + goto done; + continue; + } + + if( s->section == CPU_OBJ_SEC_UNDEF ) + { + if( find_global( globals, ng, s->name, &gi ) ) + { + if( s->binding == CPU_OBJ_BIND_WEAK ) + { + if( ( r->type == CPU_RELOC_PTR64_CODE || + r->type == CPU_RELOC_PTR64_DATA || + r->type == CPU_RELOC_CPTR64_DATA) && r->addend == 0) + { + if( source_field( objs, oi, bases, srcidx, r->offset, + &program, space, &dst, &dsz, &field, err, es)) + goto done; + if( r->type == CPU_RELOC_PTR64_CODE ) + __cpu_code_put_u64( dst + (size_t)field, 0 ); + else + __cpu_data_put_u64( dst + (size_t)field, 0 ); + continue; + } + snprintf( err, es, + "unresolved weak symbol cannot be used by this relocation: %s", + s->name); + goto done; + } + snprintf( err, es, "undefined symbol: %s", s->name ); + goto done; + } + + toi = globals[gi].obj; + tsi = globals[gi].sym; + s = &objs[toi].symbols[tsi]; + } + + tidx = sidx( s->section ); + if( tidx < 0 ) + { + snprintf( err, es, "bad relocation target section" ); + goto done; + } + + if( s->value > UINT64_MAX - bases[toi *LSEC_COUNT + (size_t)tidx] ) + { + snprintf( err, es, "symbol address overflow: %s", s->name ); + goto done; + } + sval = bases[toi * LSEC_COUNT + (size_t)tidx] + s->value; + if( add_signed( sval, r->addend, &final ) ) + { + snprintf( err, es, "relocation overflow: %s", s->name ); + goto done; + } + + if( source_field( objs, oi, bases, srcidx, r->offset, + &program, space, &dst, &dsz, &field, err, es)) + goto done; + + tseg = cpu_obj_sec_segment( s->section ); + switch( r->type ) + { + case CPU_RELOC_CODE64: + if( tseg != CPU_PTR_SEG_PROGRAM || final >= program.n ) + { + snprintf( err, es, "CODE64 target is not valid PROGRAM: %s", s->name ); + goto done; + } + __cpu_code_put_u64( dst + (size_t)field, final ); + break; + + case CPU_RELOC_DATA64: + if( tseg != CPU_PTR_SEG_DATA || final > data_mem_size ) + { + snprintf( err, es, "ADDR64 target is not valid DATA: %s", s->name ); + goto done; + } + __cpu_code_put_u64( dst + (size_t)field, final ); + break; + + case CPU_RELOC_PTR64_CODE: + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &ptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 target cannot be encoded: %s", s->name ); + goto done; + } + __cpu_code_put_u64( dst + (size_t)field, ptr ); + break; + + case CPU_RELOC_PTR64_DATA: + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &ptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 target cannot be encoded: %s", s->name ); + goto done; + } + __cpu_data_put_u64( dst + (size_t)field, ptr ); + break; + + case CPU_RELOC_CPTR64_DATA: + if( tseg != CPU_PTR_SEG_PROGRAM ) + { + snprintf( err, es, ".cptr target is not PROGRAM: %s", s->name ); + goto done; + } + if( final > CPU_PTR_OFFSET_MASK || + __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, final )) + { + snprintf( err, es, "PROGRAM PTR64 cannot be encoded: %s", s->name ); + goto done; + } + __cpu_data_put_u64( dst + (size_t)field, ptr ); + break; + + default: + snprintf( err, es, "unknown relocation type %u", r->type ); + goto done; + } + } + + /* Lifecycle is a property of an executable, never of ET_DYN. */ + if( mode != CPU_LINK_SHARED ) + { + size_t gs, gl, gf; + cpu_asm_symbol *ss, *sl, *sf = 0; + __mpu_uint64_t sv, lv, fv = 0; + int six, lix, fix; + int have_init = sec_present[LSEC_INIT]; + int have_fini = sec_present[LSEC_FINI]; + + if( find_global( globals, ng, "_start", &gs ) ) + { + snprintf( err, es, "linked executable requires _start" ); + goto done; + } + if( find_global( globals, ng, "_life", &gl ) ) + { + snprintf( err, es, "linked executable requires _life" ); + goto done; + } + + ss = &objs[globals[gs].obj].symbols[globals[gs].sym]; + sl = &objs[globals[gl].obj].symbols[globals[gl].sym]; + if( strong_lifecycle( ss, "_start", err, es ) || + strong_lifecycle( sl, "_life", err, es )) + goto done; + + six = sidx( ss->section ); + lix = sidx( sl->section ); + if( six < 0 || lix < 0 ) + { + snprintf( err, es, "bad lifecycle section" ); + goto done; + } + sv = bases[globals[gs].obj * LSEC_COUNT + (size_t)six] + ss->value; + lv = bases[globals[gl].obj * LSEC_COUNT + (size_t)lix] + sl->value; + + if( have_init ) + { + if( ss->section != CPU_OBJ_SEC_INIT || sl->section != CPU_OBJ_SEC_TEXT ) + { + snprintf( err, es, + "with .init, _start must be in .init and _life in .text"); + goto done; + } + } + else + { + if( ss->section != CPU_OBJ_SEC_TEXT || sl->section != CPU_OBJ_SEC_TEXT || sv != lv ) + { + snprintf( err, es, + "without .init, _start and _life must have the same .text address"); + goto done; + } + } + + if( have_fini ) + { + if( find_global( globals, ng, "_fini", &gf ) ) + { + snprintf( err, es, ".fini requires _fini" ); + goto done; + } + sf = &objs[globals[gf].obj].symbols[globals[gf].sym]; + if( strong_lifecycle( sf, "_fini", err, es ) || + sf->section != CPU_OBJ_SEC_FINI) + { + snprintf( err, es, "_fini must be strong GLOBAL/FUNC in .fini" ); + goto done; + } + fix = sidx( sf->section ); + fv = bases[globals[gf].obj * LSEC_COUNT + (size_t)fix] + sf->value; + } + else if( !find_global( globals, ng, "_fini", &gf ) ) + { + snprintf( err, es, "_fini is defined but no .fini section exists" ); + goto done; + } + + im->has_start = 1; + im->has_life = 1; + im->has_init = have_init; + im->has_fini = have_fini; + im->start_entry = sv; + im->life_entry = lv; + im->init_entry = have_init ? sv : 0; + im->fini_entry = have_fini ? fv : 0; + } + + im->code = program.p; + im->code_size = program.n; + program.p = 0; + im->data = space[0].p; + im->data_size = space[0].n; + space[0].p = 0; + im->data_mem_size = (size_t)data_mem_size; + im->control = space[1].p; + im->control_size = space[1].n; + space[1].p = 0; + im->input = space[2].p; + im->input_size = space[2].n; + space[2].p = 0; + im->output = space[3].p; + im->output_size = space[3].n; + space[3].p = 0; + im->trace = space[4].p; + im->trace_size = space[4].n; + space[4].p = 0; + + im->init_present = sec_present[LSEC_INIT]; + im->init_offset = sec_start[LSEC_INIT]; + im->init_size = sec_end[LSEC_INIT] - sec_start[LSEC_INIT]; + im->init_align = sec_align[LSEC_INIT]; + + im->text_present = sec_present[LSEC_TEXT]; + im->text_offset = sec_start[LSEC_TEXT]; + im->text_size = sec_end[LSEC_TEXT] - sec_start[LSEC_TEXT]; + im->text_align = sec_align[LSEC_TEXT]; + + im->fini_present = sec_present[LSEC_FINI]; + im->fini_offset = sec_start[LSEC_FINI]; + im->fini_size = sec_end[LSEC_FINI] - sec_start[LSEC_FINI]; + im->fini_align = sec_align[LSEC_FINI]; + + im->data_present = sec_present[LSEC_DATA]; + im->data_offset = sec_start[LSEC_DATA]; + im->data_section_size = sec_end[LSEC_DATA] - sec_start[LSEC_DATA]; + im->data_align = sec_align[LSEC_DATA]; + + im->bss_present = sec_present[LSEC_BSS]; + im->bss_offset = sec_start[LSEC_BSS]; + im->bss_size = sec_end[LSEC_BSS] - sec_start[LSEC_BSS]; + im->bss_align = sec_align[LSEC_BSS]; + + im->control_present = sec_present[LSEC_CONTROL]; + im->control_offset = sec_start[LSEC_CONTROL]; + im->control_section_size = sec_end[LSEC_CONTROL] - sec_start[LSEC_CONTROL]; + im->control_align = sec_align[LSEC_CONTROL]; + + im->input_present = sec_present[LSEC_INPUT]; + im->input_offset = sec_start[LSEC_INPUT]; + im->input_section_size = sec_end[LSEC_INPUT] - sec_start[LSEC_INPUT]; + im->input_align = sec_align[LSEC_INPUT]; + + im->output_present = sec_present[LSEC_OUTPUT]; + im->output_offset = sec_start[LSEC_OUTPUT]; + im->output_section_size = sec_end[LSEC_OUTPUT] - sec_start[LSEC_OUTPUT]; + im->output_align = sec_align[LSEC_OUTPUT]; + + im->trace_present = sec_present[LSEC_TRACE]; + im->trace_offset = sec_start[LSEC_TRACE]; + im->trace_section_size = sec_end[LSEC_TRACE] - sec_start[LSEC_TRACE]; + im->trace_align = sec_align[LSEC_TRACE]; + + rc = 0; + +done: + free( program.p ); + for( si = 0; si < 5; ++si ) + free( space[si].p ); + free( bases ); + free( globals ); + if( rc ) + cpu_asm_image_free( im ); + return( rc ); +} + + +static int +reloc_append_symbol( cpu_asm_object *o, const cpu_asm_symbol *s, + unsigned section, __mpu_uint64_t value, + size_t *idx, char *err, size_t es ) +{ + cpu_asm_symbol *q; + + q = (cpu_asm_symbol *)realloc( o->symbols, + (o->symbol_count + 1) * sizeof(*q) ); + if( q == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->symbols = q; + memset( &o->symbols[o->symbol_count], 0, + sizeof(o->symbols[o->symbol_count]) ); + o->symbols[o->symbol_count].name = strdup( s->name ); + if( o->symbols[o->symbol_count].name == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->symbols[o->symbol_count].section = section; + o->symbols[o->symbol_count].binding = s->binding; + o->symbols[o->symbol_count].type = s->type; + o->symbols[o->symbol_count].value = value; + o->symbols[o->symbol_count].size = s->size; + if( idx != NULL ) + *idx = o->symbol_count; + ++o->symbol_count; + return( 0 ); + +} /* End of reloc_append_symbol() */ + + +static int +reloc_find_nonlocal( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + + for( i = 0; i < o->symbol_count; ++i ) + if( o->symbols[i].binding != CPU_OBJ_BIND_LOCAL && + strcmp(o->symbols[i].name, name) == 0 ) + { + if( idx != NULL ) + *idx = i; + return( 0 ); + } + + return( -1 ); + +} /* End of reloc_find_nonlocal() */ + + +static int +reloc_merge_nonlocal( cpu_asm_object *o, const cpu_asm_symbol *s, + unsigned section, __mpu_uint64_t value, + size_t *idx, char *err, size_t es ) +{ + cpu_asm_symbol *d; + size_t i; + + if( reloc_find_nonlocal(o, s->name, &i) != 0 ) + return( reloc_append_symbol(o, s, section, value, idx, err, es) ); + + d = &o->symbols[i]; + + if( section != CPU_OBJ_SEC_UNDEF ) + { + if( d->section != CPU_OBJ_SEC_UNDEF ) + { + if( d->binding == CPU_OBJ_BIND_GLOBAL && + s->binding == CPU_OBJ_BIND_GLOBAL ) + { + snprintf( err, es, "multiple definition of global symbol: %s", s->name ); + return( -1 ); + } + + if( d->binding == CPU_OBJ_BIND_WEAK && + s->binding == CPU_OBJ_BIND_GLOBAL ) + { + d->section = section; + d->binding = s->binding; + d->type = s->type; + d->value = value; + d->size = s->size; + } + } + else + { + d->section = section; + d->binding = s->binding; + d->type = s->type; + d->value = value; + d->size = s->size; + } + } + else if( d->section == CPU_OBJ_SEC_UNDEF ) + { + if( d->binding == CPU_OBJ_BIND_WEAK && + s->binding == CPU_OBJ_BIND_GLOBAL ) + d->binding = CPU_OBJ_BIND_GLOBAL; + if( d->type == CPU_OBJ_TYPE_NOTYPE && s->type != CPU_OBJ_TYPE_NOTYPE ) + d->type = s->type; + } + + if( idx != NULL ) + *idx = i; + return( 0 ); + +} /* End of reloc_merge_nonlocal() */ + + +static int +reloc_take_section( cpu_asm_object *o, int si, lbuf *b, + __mpu_uint64_t align, int present, + char *err, size_t es ) +{ + if( !present ) + return( 0 ); + + if( align == 0 ) + align = 1; + + switch( si ) + { + case LSEC_INIT: + o->init = b->p; + o->init_size = b->n; + o->init_align = align; + o->has_init = 1; + break; + case LSEC_TEXT: + o->text = b->p; + o->text_size = b->n; + o->text_align = align; + o->has_text = 1; + break; + case LSEC_FINI: + o->fini = b->p; + o->fini_size = b->n; + o->fini_align = align; + o->has_fini = 1; + break; + case LSEC_DATA: + o->data = b->p; + o->data_size = b->n; + o->data_align = align; + o->has_data = 1; + break; + case LSEC_CONTROL: + o->control = b->p; + o->control_size = b->n; + o->control_align = align; + o->has_control = 1; + break; + case LSEC_INPUT: + o->input = b->p; + o->input_size = b->n; + o->input_align = align; + o->has_input = 1; + break; + case LSEC_OUTPUT: + o->output = b->p; + o->output_size = b->n; + o->output_align = align; + o->has_output = 1; + break; + case LSEC_TRACE: + o->trace = b->p; + o->trace_size = b->n; + o->trace_align = align; + o->has_trace = 1; + break; + default: + snprintf( err, es, "bad relocatable output section" ); + return( -1 ); + } + + b->p = NULL; + b->n = b->cap = 0; + return( 0 ); + +} /* End of reloc_take_section() */ + + +int +cpu_link_relocatable( cpu_asm_object *objs, size_t nobjs, + cpu_asm_object *out, char *err, size_t es ) +{ + lbuf sections[LSEC_COUNT] = {{0}}; + __mpu_uint64_t *bases = NULL; + __mpu_uint64_t aligns[LSEC_COUNT] = {1,1,1,1,1,1,1,1,1}; + int present[LSEC_COUNT] = {0}; + size_t *symbase = NULL; + size_t *symmap = NULL; + size_t total_symbols = 0; + size_t total_relocs = 0; + __mpu_uint64_t bss_size = 0; + size_t oi, si, ri; + int rc = -1; + + if( objs == NULL || nobjs == 0 || out == NULL ) + return( -1 ); + + memset( out, 0, sizeof(*out) ); + + if( nobjs > SIZE_MAX / LSEC_COUNT ) + { + snprintf( err, es, "too many input objects" ); + goto done; + } + + bases = (__mpu_uint64_t *)calloc( nobjs * LSEC_COUNT, sizeof(*bases) ); + symbase = (size_t *)calloc( nobjs + 1, sizeof(*symbase) ); + if( bases == NULL || symbase == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + for( oi = 0; oi < nobjs; ++oi ) + { + if( objs[oi].symbol_count > SIZE_MAX - total_symbols || + objs[oi].reloc_count > SIZE_MAX - total_relocs ) + { + snprintf( err, es, "too many symbols or relocations" ); + goto done; + } + symbase[oi] = total_symbols; + total_symbols += objs[oi].symbol_count; + total_relocs += objs[oi].reloc_count; + } + symbase[nobjs] = total_symbols; + + if( total_symbols != 0 ) + { + symmap = (size_t *)calloc( total_symbols, sizeof(*symmap) ); + if( symmap == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + + /* Merge file-backed sections independently. ET_REL owns no final DATA + guard and therefore every output section starts at offset zero. */ + for( si = LSEC_INIT; si <= LSEC_TRACE; ++si ) + for( oi = 0; oi < nobjs; ++oi ) + { + const __mpu_UNIT_t *p; + size_t n; + __mpu_uint64_t a; + int have; + + p = objdata( &objs[oi], (int)si, &n, &a, &have ); + if( !have ) + continue; + if( a == 0 ) + a = 1; + if( !cpu_melf_is_pow2(a) ) + { + snprintf( err, es, "invalid section contribution alignment" ); + goto done; + } + present[si] = 1; + if( a > aligns[si] ) + aligns[si] = a; + if( padalign(§ions[si], a) ) + { + snprintf( err, es, "cannot align relocatable output section" ); + goto done; + } + bases[oi * LSEC_COUNT + si] = sections[si].n; + if( put(§ions[si], p, n) ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + + /* .bss is merged as NOBITS and therefore advances only its logical size. */ + for( oi = 0; oi < nobjs; ++oi ) + { + __mpu_uint64_t a = objs[oi].bss_align ? objs[oi].bss_align : 1; + __mpu_uint64_t aligned; + int have = objs[oi].has_bss || objs[oi].bss_size; + + if( !have ) + continue; + if( !cpu_melf_is_pow2(a) || align_u64(bss_size, a, &aligned) ) + { + snprintf( err, es, "cannot align relocatable .bss contribution" ); + goto done; + } + bss_size = aligned; + present[LSEC_BSS] = 1; + if( a > aligns[LSEC_BSS] ) + aligns[LSEC_BSS] = a; + bases[oi * LSEC_COUNT + LSEC_BSS] = bss_size; + if( (__mpu_uint64_t)objs[oi].bss_size > UINT64_MAX - bss_size ) + { + snprintf( err, es, "relocatable .bss layout overflow" ); + goto done; + } + bss_size += (__mpu_uint64_t)objs[oi].bss_size; + } + + if( bss_size > SIZE_MAX ) + { + snprintf( err, es, "relocatable .bss exceeds supported host size" ); + goto done; + } + + /* Build one canonical non-local symbol per name. Defined GLOBAL beats + WEAK, a definition beats an undefined reference, and LOCAL symbols + remain private even when different input objects use the same name. */ + for( oi = 0; oi < nobjs; ++oi ) + for( si = 0; si < objs[oi].symbol_count; ++si ) + { + const cpu_asm_symbol *s = &objs[oi].symbols[si]; + unsigned section = s->section; + __mpu_uint64_t value = s->value; + size_t mi; + + if( section != CPU_OBJ_SEC_UNDEF ) + { + int ix = sidx( section ); + if( ix < 0 || value > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] ) + { + snprintf( err, es, "bad relocatable symbol placement: %s", s->name ); + goto done; + } + value += bases[oi * LSEC_COUNT + (size_t)ix]; + } + + if( s->binding == CPU_OBJ_BIND_LOCAL ) + { + if( reloc_append_symbol(out, s, section, value, &mi, err, es) ) + goto done; + } + else if( reloc_merge_nonlocal(out, s, section, value, + &mi, err, es) ) + goto done; + + symmap[symbase[oi] + si] = mi; + } + + if( total_relocs != 0 ) + { + out->relocs = (cpu_asm_reloc *)calloc( total_relocs, + sizeof(*out->relocs) ); + if( out->relocs == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + + for( oi = 0; oi < nobjs; ++oi ) + for( ri = 0; ri < objs[oi].reloc_count; ++ri ) + { + const cpu_asm_reloc *r = &objs[oi].relocs[ri]; + cpu_asm_reloc *d; + int ix = sidx( r->section ); + + if( r->symbol_index >= objs[oi].symbol_count || ix < 0 ) + { + snprintf( err, es, "bad relocatable relocation" ); + goto done; + } + if( r->offset > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] ) + { + snprintf( err, es, "relocatable relocation offset overflow" ); + goto done; + } + + d = &out->relocs[out->reloc_count++]; + d->section = r->section; + d->type = r->type; + d->symbol_index = symmap[symbase[oi] + r->symbol_index]; + d->offset = bases[oi * LSEC_COUNT + (size_t)ix] + r->offset; + d->addend = r->addend; + } + + for( si = LSEC_INIT; si <= LSEC_TRACE; ++si ) + if( reloc_take_section(out, (int)si, §ions[si], aligns[si], + present[si], err, es) ) + goto done; + + if( present[LSEC_BSS] ) + { + out->has_bss = 1; + out->bss_size = (size_t)bss_size; + out->bss_align = aligns[LSEC_BSS]; + } + + rc = 0; + +done: + for( si = 0; si < LSEC_COUNT; ++si ) + free( sections[si].p ); + free( bases ); + free( symbase ); + free( symmap ); + if( rc != 0 ) + cpu_asm_object_free( out ); + return( rc ); + +} /* End of cpu_link_relocatable() */ + + +int +cpu_link_objects( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *im, + char *err, size_t es) +{ + return( cpu_link_objects_mode( objs, nobjs, im, CPU_LINK_STATIC_EXEC, err, es ) ); +} diff --git a/utils/mcpu-link.h b/utils/mcpu-link.h new file mode 100644 index 0000000..5d28158 --- /dev/null +++ b/utils/mcpu-link.h @@ -0,0 +1,18 @@ +#ifndef __CPU_LINK_H +#define __CPU_LINK_H +#include "mcpu-asm.h" + +typedef enum cpu_link_mode +{ + CPU_LINK_STATIC_EXEC = 0, + CPU_LINK_DYNAMIC_EXEC, + CPU_LINK_SHARED, + CPU_LINK_RELOCATABLE +} cpu_link_mode; + +int cpu_link_objects( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *image, char *err, size_t errsz ); +int cpu_link_objects_mode( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *image, + cpu_link_mode mode, char *err, size_t errsz); +int cpu_link_relocatable( cpu_asm_object *objs, size_t nobjs, cpu_asm_object *object, + char *err, size_t errsz ); +#endif diff --git a/utils/mcpu-melf.c b/utils/mcpu-melf.c new file mode 100644 index 0000000..1178c6f --- /dev/null +++ b/utils/mcpu-melf.c @@ -0,0 +1,435 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mcpu-melf.h" + +int +cpu_melf_data_encoding( void ) +{ +#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1 + return( MELF_DATA_LSB ); +#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1 + return( MELF_DATA_MSB ); +#else +# error "Unsupported CPU_DATA_BYTE_ORDER" +#endif +} + +static int +is_le( int data ) +{ + return( data == MELF_DATA_LSB ); +} + +void +cpu_melf_put16( unsigned char *p, __mpu_uint16_t v, int data ) +{ + if( is_le( data ) ) + { + p[0] = (unsigned char)v; + p[1] = (unsigned char)(v >> 8); + } + else + { + p[0] = (unsigned char)(v >> 8); + p[1] = (unsigned char)v; + } +} + +void +cpu_melf_put32( unsigned char *p, __mpu_uint32_t v, int data ) +{ + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 4; ++i ) + p[i] = (unsigned char)(v >> (8 * i)); + else + for( i = 0; i < 4; ++i ) + p[i] = (unsigned char)(v >> (8 * (3 - i))); +} + +void +cpu_melf_put64( unsigned char *p, __mpu_uint64_t v, int data ) +{ + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 8; ++i ) + p[i] = (unsigned char)(v >> (8 * i)); + else + for( i = 0; i < 8; ++i ) + p[i] = (unsigned char)(v >> (8 * (7 - i))); +} + +__mpu_uint16_t +cpu_melf_get16( const unsigned char *p, int data ) +{ + if( is_le( data ) ) + return (__mpu_uint16_t)(p[0] | ((__mpu_uint16_t)p[1] << 8)); + return (__mpu_uint16_t)(((__mpu_uint16_t)p[0] << 8) | p[1]); +} + +__mpu_uint32_t +cpu_melf_get32( const unsigned char *p, int data ) +{ + __mpu_uint32_t v = 0; + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 4; ++i ) + v |= (__mpu_uint32_t)p[i] << (8 * i); + else + for( i = 0; i < 4; ++i ) + v = (v << 8) | p[i]; + return( v ); +} + +__mpu_uint64_t +cpu_melf_get64( const unsigned char *p, int data ) +{ + __mpu_uint64_t v = 0; + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 8; ++i ) + v |= (__mpu_uint64_t)p[i] << (8 * i); + else + for( i = 0; i < 8; ++i ) + v = (v << 8) | p[i]; + return( v ); +} + +int +cpu_melf_span( size_t n, __mpu_uint64_t off, __mpu_uint64_t len ) +{ + return( off <= (__mpu_uint64_t)n && len <= (__mpu_uint64_t)n - off ); +} + +int +cpu_melf_is_pow2( __mpu_uint64_t v ) +{ + return( v != 0 && ( v & ( v - 1 ) ) == 0 ); +} + +__mpu_uint64_t +cpu_melf_align_up( __mpu_uint64_t v, __mpu_uint64_t a, int *ov ) +{ + __mpu_uint64_t r; + + if( ov ) + *ov = 0; + if( a <= 1 ) + return( v ); + if( !cpu_melf_is_pow2( a ) ) + { + if( ov ) + *ov = 1; + return( 0 ); + } + + r = v & (a - 1); + if( !r ) + return( v ); + if( v > UINT64_MAX - ( a - r ) ) + { + if( ov ) + *ov = 1; + return( 0 ); + } + return( v + ( a - r ) ); +} + +int +cpu_melf_readall( const char *path, unsigned char **pp, size_t *pn, + char *err, size_t es) +{ + FILE *f; + long z; + unsigned char *p; + + if( !path || !pp || !pn ) + return( -1 ); + + f = fopen( path, "rb" ); + if( !f ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( fseek( f, 0, SEEK_END ) || ( z = ftell( f ) ) < 0 || fseek( f, 0, SEEK_SET ) ) + { + snprintf( err, es, "cannot size %s", path ); + fclose( f ); + return( -1 ); + } + + p = (unsigned char *)malloc( z ? (size_t)z : 1 ); + if( !p ) + { + snprintf( err, es, "out of memory" ); + fclose( f ); + return( -1 ); + } + if( z && fread( p, 1, (size_t)z, f ) != (size_t)z ) + { + snprintf( err, es, "cannot read %s", path ); + free( p ); + fclose( f ); + return( -1 ); + } + if( fclose( f ) ) + { + snprintf( err, es, "cannot close %s", path ); + free( p ); + return( -1 ); + } + + *pp = p; + *pn = (size_t)z; + return( 0 ); +} + +int +cpu_melf_writeall( const char *path, const void *data, size_t n, + char *err, size_t es) +{ + FILE *f = fopen( path, "wb" ); + + if( !f ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( n && fwrite( data, 1, n, f ) != n ) + { + snprintf( err, es, "cannot write %s", path ); + fclose( f ); + return( -1 ); + } + if( fclose( f ) ) + { + snprintf( err, es, "cannot close %s", path ); + return( -1 ); + } + return( 0 ); +} + +void +cpu_melf_fill_target( MElf_McpuTarget *t ) +{ + memset( t, 0, sizeof( *t ) ); + t->mt_version = MELF_MCPU_TARGET_VERSION_CURRENT; + t->mt_size = MELF_MCPU_TARGET_SIZE; + t->mt_isa_revision = MELF_MCPU_ISA_REVISION_CURRENT; + t->mt_abi_revision = MELF_MCPU_ABI_REVISION_CURRENT; + t->mt_program_model = MELF_MCPU_MODEL_NONE; + t->mt_data_byte_order = (__mpu_uint32_t)cpu_melf_data_encoding(); +#if CPU_DATA_WORD_ORDER_LITTLE_ENDIAN == 1 + t->mt_data_word_order = MELF_MCPU_WORD_ORDER_LSB; +#elif CPU_DATA_WORD_ORDER_BIG_ENDIAN == 1 + t->mt_data_word_order = MELF_MCPU_WORD_ORDER_MSB; +#else +# error "Unsupported CPU_DATA_WORD_ORDER" +#endif + t->mt_integer_model = MELF_MCPU_INTEGER_MODEL_TWOS_COMPLEMENT; + t->mt_address_model = MELF_MCPU_ADDRESS_MODEL_PTR64_SEG4_OFF60; + t->mt_register_model = MELF_MCPU_REGISTER_MODEL_REG24_512K; + t->mt_real_model = MELF_MCPU_REAL_MODEL_LIBMPU; + t->mt_complex_model = MELF_MCPU_COMPLEX_MODEL_LIBMPU; + t->mt_max_align = MELF_MCPU_ABI_MAX_ALIGN; + t->mt_flags = MELF_MCPU_TARGET_FLAGS_NONE; + t->mt_required_features = MELF_MCPU_REQUIRED_FEATURES_NONE; +} + +void +cpu_melf_encode_target( unsigned char out[MELF_MCPU_TARGET_SIZE], + const MElf_McpuTarget *t, int data) +{ + memset( out, 0, MELF_MCPU_TARGET_SIZE ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_VERSION_OFF, t->mt_version, data ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_SIZE_OFF, t->mt_size, data ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ISA_REVISION_OFF, + t->mt_isa_revision, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ABI_REVISION_OFF, + t->mt_abi_revision, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_PROGRAM_MODEL_OFF, + t->mt_program_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_DATA_BYTE_ORDER_OFF, + t->mt_data_byte_order, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_DATA_WORD_ORDER_OFF, + t->mt_data_word_order, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_INTEGER_MODEL_OFF, + t->mt_integer_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ADDRESS_MODEL_OFF, + t->mt_address_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_REGISTER_MODEL_OFF, + t->mt_register_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_REAL_MODEL_OFF, + t->mt_real_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_COMPLEX_MODEL_OFF, + t->mt_complex_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_MAX_ALIGN_OFF, + t->mt_max_align, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_FLAGS_OFF, + t->mt_flags, data); + cpu_melf_put64( out + MELF_MCPU_TARGET_MT_REQUIRED_FEATURES_OFF, + t->mt_required_features, data); +} + +int +cpu_melf_decode_target( const unsigned char in[MELF_MCPU_TARGET_SIZE], + MElf_McpuTarget *t, int data) +{ + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + return( -1 ); + + memset( t, 0, sizeof( *t ) ); + t->mt_version = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_VERSION_OFF, data ); + t->mt_size = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_SIZE_OFF, data ); + t->mt_isa_revision = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ISA_REVISION_OFF, data); + t->mt_abi_revision = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ABI_REVISION_OFF, data); + t->mt_program_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_PROGRAM_MODEL_OFF, data); + t->mt_data_byte_order = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_DATA_BYTE_ORDER_OFF, data); + t->mt_data_word_order = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_DATA_WORD_ORDER_OFF, data); + t->mt_integer_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_INTEGER_MODEL_OFF, data); + t->mt_address_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ADDRESS_MODEL_OFF, data); + t->mt_register_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_REGISTER_MODEL_OFF, data); + t->mt_real_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_REAL_MODEL_OFF, data); + t->mt_complex_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_COMPLEX_MODEL_OFF, data); + t->mt_max_align = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_MAX_ALIGN_OFF, data); + t->mt_flags = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_FLAGS_OFF, data ); + t->mt_required_features = cpu_melf_get64( + in + MELF_MCPU_TARGET_MT_REQUIRED_FEATURES_OFF, data); + return( 0 ); +} + +int +cpu_melf_validate_target( const MElf_McpuTarget *t, int data, + char *err, size_t es) +{ + MElf_McpuTarget x; + + cpu_melf_fill_target( &x ); +#define EQ(FIELD) \ + do { \ + if(t->FIELD != x.FIELD) { \ + snprintf(err, es, "incompatible MCPU target profile field %s", #FIELD); \ + return -1; \ + } \ + } while(0) + + EQ( mt_version ); + if( t->mt_size != MELF_MCPU_TARGET_SIZE ) + { + snprintf( err, es, "bad .mcpu.target record size" ); + return( -1 ); + } + + EQ( mt_isa_revision ); + EQ( mt_abi_revision ); + EQ( mt_program_model ); + EQ( mt_data_byte_order ); + EQ( mt_data_word_order ); + EQ( mt_integer_model ); + EQ( mt_address_model ); + EQ( mt_register_model ); + EQ( mt_real_model ); + EQ( mt_complex_model ); + EQ( mt_max_align ); + EQ( mt_flags ); + EQ( mt_required_features ); +#undef EQ + + if( (int)t->mt_data_byte_order != data ) + { + snprintf( err, es, "MCPU target byte order disagrees with EI_DATA" ); + return( -1 ); + } + + return( 0 ); +} + +void +cpu_melf_fill_lifecycle( MElf_McpuLifecycle *l ) +{ + memset( l, 0, sizeof( *l ) ); + l->ml_version = MELF_MCPU_LIFECYCLE_VERSION_CURRENT; + l->ml_size = MELF_MCPU_LIFECYCLE_SIZE; + l->ml_abi = MELF_MCPU_LIFECYCLE_ABI_CURRENT; + l->ml_flags = MELF_MCPU_LIFECYCLE_ABI1_FLAGS; +} + +void +cpu_melf_encode_lifecycle( unsigned char out[MELF_MCPU_LIFECYCLE_SIZE], + const MElf_McpuLifecycle *l, int data) +{ + memset( out, 0, MELF_MCPU_LIFECYCLE_SIZE ); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_VERSION_OFF, + l->ml_version, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_SIZE_OFF, + l->ml_size, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_ABI_OFF, + l->ml_abi, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_RESERVED0_OFF, + l->ml_reserved0, data); + cpu_melf_put64( out + MELF_MCPU_LIFECYCLE_ML_FLAGS_OFF, + l->ml_flags, data); + cpu_melf_put64( out + MELF_MCPU_LIFECYCLE_ML_RESERVED1_OFF, + l->ml_reserved1, data); +} + +int +cpu_melf_decode_lifecycle( const unsigned char in[MELF_MCPU_LIFECYCLE_SIZE], + MElf_McpuLifecycle *l, int data) +{ + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + return( -1 ); + + memset( l, 0, sizeof( *l ) ); + l->ml_version = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_VERSION_OFF, data); + l->ml_size = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_SIZE_OFF, data); + l->ml_abi = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_ABI_OFF, data); + l->ml_reserved0 = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_RESERVED0_OFF, data); + l->ml_flags = cpu_melf_get64( + in + MELF_MCPU_LIFECYCLE_ML_FLAGS_OFF, data); + l->ml_reserved1 = cpu_melf_get64( + in + MELF_MCPU_LIFECYCLE_ML_RESERVED1_OFF, data); + return( 0 ); +} + +int +cpu_melf_validate_lifecycle( const MElf_McpuLifecycle *l, + char *err, size_t es) +{ + if( l->ml_version != MELF_MCPU_LIFECYCLE_VERSION_CURRENT || + l->ml_size != MELF_MCPU_LIFECYCLE_SIZE || + l->ml_abi != MELF_MCPU_LIFECYCLE_ABI_CURRENT || + l->ml_reserved0 != 0 || + l->ml_flags != MELF_MCPU_LIFECYCLE_ABI1_FLAGS || + l->ml_reserved1 != 0) + { + snprintf( err, es, "unsupported or malformed .mcpu.lifecycle record" ); + return( -1 ); + } + return( 0 ); +} diff --git a/utils/mcpu-melf.h b/utils/mcpu-melf.h new file mode 100644 index 0000000..f6c234f --- /dev/null +++ b/utils/mcpu-melf.h @@ -0,0 +1,42 @@ +#ifndef __CPU_MELF_H +#define __CPU_MELF_H + +#include <stddef.h> +#include <stdio.h> +#include <libmcpu.h> +#include <mcpu-elf.h> + +int cpu_melf_data_encoding( void ); +void cpu_melf_put16( unsigned char *p, __mpu_uint16_t v, int data ); +void cpu_melf_put32( unsigned char *p, __mpu_uint32_t v, int data ); +void cpu_melf_put64( unsigned char *p, __mpu_uint64_t v, int data ); +__mpu_uint16_t cpu_melf_get16( const unsigned char *p, int data ); +__mpu_uint32_t cpu_melf_get32( const unsigned char *p, int data ); +__mpu_uint64_t cpu_melf_get64( const unsigned char *p, int data ); + +int cpu_melf_span( size_t file_size, __mpu_uint64_t off, __mpu_uint64_t len ); +__mpu_uint64_t cpu_melf_align_up( __mpu_uint64_t v, __mpu_uint64_t align, int *overflow ); +int cpu_melf_readall( const char *path, unsigned char **data, size_t *size, + char *err, size_t errsz); +int cpu_melf_writeall( const char *path, const void *data, size_t size, + char *err, size_t errsz); + +void cpu_melf_fill_target( MElf_McpuTarget *t ); +void cpu_melf_encode_target( unsigned char out[MELF_MCPU_TARGET_SIZE], + const MElf_McpuTarget *t, int data); +int cpu_melf_decode_target( const unsigned char in[MELF_MCPU_TARGET_SIZE], + MElf_McpuTarget *t, int data); +int cpu_melf_validate_target( const MElf_McpuTarget *t, int data, + char *err, size_t errsz); + +void cpu_melf_fill_lifecycle( MElf_McpuLifecycle *l ); +void cpu_melf_encode_lifecycle( unsigned char out[MELF_MCPU_LIFECYCLE_SIZE], + const MElf_McpuLifecycle *l, int data); +int cpu_melf_decode_lifecycle( const unsigned char in[MELF_MCPU_LIFECYCLE_SIZE], + MElf_McpuLifecycle *l, int data); +int cpu_melf_validate_lifecycle( const MElf_McpuLifecycle *l, + char *err, size_t errsz); + +int cpu_melf_is_pow2( __mpu_uint64_t v ); + +#endif diff --git a/utils/mcpu-nm.c b/utils/mcpu-nm.c new file mode 100644 index 0000000..63c3bfd --- /dev/null +++ b/utils/mcpu-nm.c @@ -0,0 +1,716 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct mcpu_nm_shdr +{ + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint64_t entsize; +} mcpu_nm_shdr; + + +typedef struct mcpu_nm_file +{ + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t shnum; + mcpu_nm_shdr *shdr; +} mcpu_nm_file; + + +typedef struct mcpu_nm_symbol +{ + char *name; + __mpu_uint64_t value; + __mpu_uint64_t size; + __mpu_uint16_t shndx; + unsigned int bind; + unsigned int type; + char code; + size_t order; +} mcpu_nm_symbol; + + +typedef struct mcpu_nm_symbols +{ + mcpu_nm_symbol *symbol; + size_t count; +} mcpu_nm_symbols; + + +typedef struct mcpu_nm_options +{ + int all_symbols; + int extern_only; + int undefined_only; + int numeric_sort; + int reverse_sort; + int no_sort; + int dynamic; + int print_file_name; +} mcpu_nm_options; + + +static const mcpu_nm_options *sort_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file(s)\n\n" + "List symbols from LibMCPU MELF objects, executables, shared objects and archives.\n\n" + "Options:\n" + " -a, --debug-syms - display all named symbols, including FILE/SECTION\n" + " -g, --extern-only - display only external GLOBAL/WEAK symbols\n" + " -u, --undefined-only - display only undefined symbols\n" + " -n, --numeric-sort - sort symbols numerically by value\n" + " -r, --reverse-sort - reverse the selected sort order\n" + " -p, --no-sort - preserve symbol-table order\n" + " -D, --dynamic - display .dynsym instead of .symtab\n" + " -A, --print-file-name - prefix each symbol with its input file name\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static void +nm_file_free( mcpu_nm_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of nm_file_free() */ + + +static void +nm_symbols_free( mcpu_nm_symbols *symbols ) +{ + size_t i; + + if( !symbols ) + return; + for( i = 0; i < symbols->count; ++i ) + free( symbols->symbol[i].name ); + free( symbols->symbol ); + memset( symbols, 0, sizeof( *symbols ) ); + +} /* End of nm_symbols_free() */ + + +static void +get_shdr( const unsigned char *p, int data, mcpu_nm_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static int +nm_file_open_data( const unsigned char *data, size_t size, mcpu_nm_file *file, + char *err, size_t errsz ) +{ + __mpu_uint64_t shoff; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ) != + MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "unsupported MELF64 header size" ); + return( -1 ); + } + + shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + + if( !file->shnum ) + return( 0 ); + if( shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( size, shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_nm_shdr *)calloc( file->shnum, sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + get_shdr( data + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + + return( 0 ); + +} /* End of nm_file_open_data() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( NULL ); + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of string_at() */ + + +static int +symbol_code( const mcpu_nm_file *file, unsigned int bind, + __mpu_uint16_t shndx, char *code ) +{ + char c; + + if( bind == MELF_STB_WEAK ) + { + *code = shndx == MELF_SHN_UNDEF ? 'w' : 'W'; + return( 0 ); + } + if( shndx == MELF_SHN_UNDEF ) + { + *code = 'U'; + return( 0 ); + } + if( shndx == MELF_SHN_ABS ) + c = 'A'; + else if( shndx == MELF_SHN_COMMON ) + c = 'C'; + else if( shndx >= file->shnum ) + c = '?'; + else if( file->shdr[shndx].flags & MELF_SHF_EXECINSTR ) + c = 'T'; + else if( file->shdr[shndx].type == MELF_SHT_NOBITS && + (file->shdr[shndx].flags & MELF_SHF_WRITE) ) + c = 'B'; + else if( file->shdr[shndx].flags & MELF_SHF_WRITE ) + c = 'D'; + else if( file->shdr[shndx].flags & MELF_SHF_ALLOC ) + c = 'R'; + else + c = 'N'; + + if( bind == MELF_STB_LOCAL && c >= 'A' && c <= 'Z' ) + c = (char)(c - 'A' + 'a'); + *code = c; + return( c == '?' ? -1 : 0 ); + +} /* End of symbol_code() */ + + +static int +symbols_append( mcpu_nm_symbols *symbols, const char *name, + __mpu_uint64_t value, __mpu_uint64_t size, + __mpu_uint16_t shndx, unsigned int bind, unsigned int type, + char code, size_t order, char *err, size_t errsz ) +{ + mcpu_nm_symbol *q; + char *copy; + + q = (mcpu_nm_symbol *)realloc( symbols->symbol, + (symbols->count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + copy = strdup( name ); + if( !copy ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + symbols->symbol = q; + q = &symbols->symbol[symbols->count++]; + memset( q, 0, sizeof( *q ) ); + q->name = copy; + q->value = value; + q->size = size; + q->shndx = shndx; + q->bind = bind; + q->type = type; + q->code = code; + q->order = order; + return( 0 ); + +} /* End of symbols_append() */ + + +static int +collect_table( const mcpu_nm_file *file, size_t table_index, + const mcpu_nm_options *options, mcpu_nm_symbols *symbols, + size_t *order, char *err, size_t errsz ) +{ + const mcpu_nm_shdr *sh = &file->shdr[table_index]; + const mcpu_nm_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t i; + + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->size > SIZE_MAX || !cpu_melf_span( file->size, sh->offset, sh->size ) ) + { + snprintf( err, errsz, "malformed symbol table" ); + return( -1 ); + } + if( sh->link >= file->shnum ) + { + snprintf( err, errsz, "symbol table has invalid string-table link" ); + return( -1 ); + } + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "malformed symbol string table" ); + return( -1 ); + } + + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( i = 0; i < count; ++i ) + { + const unsigned char *p; + __mpu_uint32_t name_off; + unsigned int bind; + unsigned int type; + __mpu_uint16_t shndx; + __mpu_uint64_t value; + __mpu_uint64_t ssize; + const char *name; + char code; + + p = file->data + (size_t)sh->offset + i * MELF64_SYM_SIZE; + name_off = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + bind = MELF64_ST_BIND( p[MELF64_SYM_ST_INFO_OFF] ); + type = MELF64_ST_TYPE( p[MELF64_SYM_ST_INFO_OFF] ); + shndx = cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + value = cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + ssize = cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, file->encoding ); + name = string_at( strtab, strsize, name_off ); + + if( !name ) + { + snprintf( err, errsz, "symbol has invalid name offset" ); + return( -1 ); + } + if( !*name ) + continue; + if( !options->all_symbols && + (type == MELF_STT_FILE || type == MELF_STT_SECTION) ) + continue; + if( options->extern_only && bind == MELF_STB_LOCAL ) + continue; + if( options->undefined_only && shndx != MELF_SHN_UNDEF ) + continue; + if( symbol_code( file, bind, shndx, &code ) ) + { + snprintf( err, errsz, "symbol '%s' has invalid section index", name ); + return( -1 ); + } + + if( symbols_append( symbols, name, value, ssize, shndx, bind, type, + code, (*order)++, err, errsz ) ) + return( -1 ); + } + return( 0 ); + +} /* End of collect_table() */ + + +static int +collect_symbols( const unsigned char *data, size_t size, + const mcpu_nm_options *options, mcpu_nm_symbols *symbols, + char *err, size_t errsz ) +{ + mcpu_nm_file file; + size_t i; + size_t order = 0; + int want; + + memset( symbols, 0, sizeof( *symbols ) ); + if( nm_file_open_data( data, size, &file, err, errsz ) ) + return( -1 ); + + want = options->dynamic ? MELF_SHT_DYNSYM : MELF_SHT_SYMTAB; + for( i = 0; i < file.shnum; ++i ) + { + if( file.shdr[i].type != (unsigned int)want ) + continue; + if( collect_table( &file, i, options, symbols, &order, err, errsz ) ) + { + nm_file_free( &file ); + nm_symbols_free( symbols ); + return( -1 ); + } + } + + nm_file_free( &file ); + return( 0 ); + +} /* End of collect_symbols() */ + + +static int +compare_symbols( const void *ap, const void *bp ) +{ + const mcpu_nm_symbol *a = (const mcpu_nm_symbol *)ap; + const mcpu_nm_symbol *b = (const mcpu_nm_symbol *)bp; + int c; + + if( sort_options->numeric_sort ) + { + if( a->value < b->value ) + c = -1; + else if( a->value > b->value ) + c = 1; + else + { + c = strcmp( a->name, b->name ); + if( !c ) + c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0; + } + } + else + { + c = strcmp( a->name, b->name ); + if( !c ) + c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0; + } + + return( sort_options->reverse_sort ? -c : c ); + +} /* End of compare_symbols() */ + + +static void +print_one_symbol( const mcpu_nm_symbol *symbol, const char *prefix, + const mcpu_nm_options *options ) +{ + if( options->print_file_name && prefix ) + printf( "%s: ", prefix ); + + if( symbol->shndx == MELF_SHN_UNDEF ) + printf( "%16s %c %s\n", "", symbol->code, symbol->name ); + else + printf( "%016" PRIx64 " %c %s\n", + (uint64_t)symbol->value, symbol->code, symbol->name ); + +} /* End of print_one_symbol() */ + + +static int +print_symbols_data( const unsigned char *data, size_t size, + const char *header, const char *prefix, + const mcpu_nm_options *options, int show_header, + char *err, size_t errsz ) +{ + mcpu_nm_symbols symbols; + size_t i; + + if( collect_symbols( data, size, options, &symbols, err, errsz ) ) + return( -1 ); + + if( !options->no_sort && symbols.count > 1 ) + { + sort_options = options; + qsort( symbols.symbol, symbols.count, sizeof( *symbols.symbol ), + compare_symbols ); + } + + if( show_header && !options->print_file_name ) + printf( "%s:\n", header ); + for( i = 0; i < symbols.count; ++i ) + print_one_symbol( &symbols.symbol[i], prefix, options ); + + nm_symbols_free( &symbols ); + return( 0 ); + +} /* End of print_symbols_data() */ + + +static int +is_archive_file( const unsigned char *data, size_t size ) +{ + static const unsigned char magic[] = "!<arch>\n"; + + return( size >= sizeof( magic ) - 1 && + !memcmp( data, magic, sizeof( magic ) - 1 ) ); + +} /* End of is_archive_file() */ + + +static int +process_archive( const char *path, const mcpu_nm_options *options, + char *err, size_t errsz ) +{ + mcpu_archive archive; + size_t i; + + if( mcpu_archive_read( path, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + const mcpu_archive_member *member = &archive.members[i]; + char label[1024]; + + if( snprintf( label, sizeof( label ), "%s:%s", path, member->name ) >= + (int)sizeof( label ) ) + { + snprintf( err, errsz, "archive/member name is too long" ); + mcpu_archive_free( &archive ); + return( -1 ); + } + + if( print_symbols_data( member->data, member->size, label, label, + options, 1, err, errsz ) ) + { + mcpu_archive_free( &archive ); + return( -1 ); + } + if( !options->print_file_name && i + 1 < archive.member_count ) + printf( "\n" ); + } + + mcpu_archive_free( &archive ); + return( 0 ); + +} /* End of process_archive() */ + + +static int +process_path( const char *path, const mcpu_nm_options *options, + int show_header, char *err, size_t errsz ) +{ + unsigned char *data = NULL; + size_t size = 0; + int rc; + + if( cpu_melf_readall( path, &data, &size, err, errsz ) ) + return( -1 ); + + if( is_archive_file( data, size ) ) + { + free( data ); + return( process_archive( path, options, err, errsz ) ); + } + + rc = print_symbols_data( data, size, path, path, options, + show_header, err, errsz ); + free( data ); + return( rc ); + +} /* End of process_path() */ + + +static int +parse_short_options( const char *arg, mcpu_nm_options *options ) +{ + const char *p = arg + 1; + + while( *p ) + { + switch( *p ) + { + case 'a': options->all_symbols = 1; break; + case 'g': options->extern_only = 1; break; + case 'u': options->undefined_only = 1; break; + case 'n': options->numeric_sort = 1; options->no_sort = 0; break; + case 'r': options->reverse_sort = 1; break; + case 'p': options->no_sort = 1; options->numeric_sort = 0; break; + case 'D': options->dynamic = 1; break; + case 'A': options->print_file_name = 1; break; + default: return( -1 ); + } + ++p; + } + return( 0 ); + +} /* End of parse_short_options() */ + + +int +main( int argc, char **argv ) +{ + mcpu_nm_options options; + char **files = NULL; + size_t file_count = 0; + size_t file_capacity = 0; + int i; + int rc = 0; + + memset( &options, 0, sizeof( options ) ); + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free( files ); + return( 0 ); + } + else if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free( files ); + return( rc == 0 ? 0 : 1 ); + } + else if( !strcmp( arg, "--debug-syms" ) ) + options.all_symbols = 1; + else if( !strcmp( arg, "--extern-only" ) ) + options.extern_only = 1; + else if( !strcmp( arg, "--undefined-only" ) ) + options.undefined_only = 1; + else if( !strcmp( arg, "--numeric-sort" ) ) + { + options.numeric_sort = 1; + options.no_sort = 0; + } + else if( !strcmp( arg, "--reverse-sort" ) ) + options.reverse_sort = 1; + else if( !strcmp( arg, "--no-sort" ) ) + { + options.no_sort = 1; + options.numeric_sort = 0; + } + else if( !strcmp( arg, "--dynamic" ) ) + options.dynamic = 1; + else if( !strcmp( arg, "--print-file-name" ) ) + options.print_file_name = 1; + else if( arg[0] == '-' && arg[1] != '\0' ) + { + if( parse_short_options( arg, &options ) ) + { + fprintf( stderr, "mcpu-nm: unknown option '%s'\n", arg ); + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + } + else + { + if( file_count == file_capacity ) + { + size_t next = file_capacity ? file_capacity * 2U : 4U; + char **q = (char **)realloc( files, next * sizeof( *q ) ); + + if( !q ) + { + fprintf( stderr, "mcpu-nm: out of memory\n" ); + free( files ); + return( 1 ); + } + files = q; + file_capacity = next; + } + files[file_count++] = argv[i]; + } + } + + if( !file_count ) + { + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + + for( i = 0; i < (int)file_count; ++i ) + { + char err[512]; + + if( i && !options.print_file_name ) + printf( "\n" ); + if( process_path( files[i], &options, + file_count > 1, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-nm: %s: %s\n", files[i], err ); + rc = 1; + } + } + + free( files ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-objcopy.c b/utils/mcpu-objcopy.c new file mode 100644 index 0000000..c977140 --- /dev/null +++ b/utils/mcpu-objcopy.c @@ -0,0 +1,596 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" +#include "mcpu-version.h" + + +typedef struct cli_action +{ + mcpu_object_edit_action edit; + char *name; + char *new_name; + unsigned char *data; +} cli_action; + + +typedef struct dump_action +{ + char *name; + char *path; +} dump_action; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] INPUT OUTPUT\n\n" + "Copy and safely edit LibMCPU MELF files and deterministic archives.\n" + "The output format is always MELF. Runtime-visible sections and frozen\n" + "MELF ABI3 execution metadata cannot be removed, renamed or updated.\n\n" + "Options:\n" + " -j, --only-section=NAME - copy only the named sections\n" + " -R, --remove-section=NAME - remove a safe non-runtime section\n" + " --dump-section=NAME=FILE - write section contents to FILE\n" + " --add-section=NAME=FILE - add non-ALLOC SHT_PROGBITS metadata\n" + " --update-section=NAME=FILE - replace safe non-ALLOC section contents\n" + " --rename-section=OLD=NEW - rename a safe non-ALLOC section\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +append_string( char ***items, size_t *count, const char *s ) +{ + char **q; + char *p; + + if( !s || !*s ) + return( -1 ); + q = (char **)realloc( *items, (*count + 1) * sizeof( *q ) ); + if( !q ) + return( -1 ); + p = strdup( s ); + if( !p ) + return( -1 ); + *items = q; + (*items)[(*count)++] = p; + return( 0 ); + +} /* End of append_string() */ + + +static void +free_strings( char **items, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + free( items[i] ); + free( items ); + +} /* End of free_strings() */ + + +static int +split_pair( const char *arg, char **left, char **right ) +{ + const char *eq = strchr( arg, '=' ); + size_t n; + + if( !eq || eq == arg || !eq[1] ) + return( -1 ); + n = (size_t)( eq - arg ); + *left = (char *)malloc( n + 1 ); + if( !*left ) + return( -1 ); + memcpy( *left, arg, n ); + (*left)[n] = 0; + *right = strdup( eq + 1 ); + if( !*right ) + { + free( *left ); + *left = NULL; + return( -1 ); + } + return( 0 ); + +} /* End of split_pair() */ + + +static int +append_edit_action( cli_action **actions, size_t *count, + mcpu_object_edit_action_type type, + const char *spec, int needs_file, + char *err, size_t errsz ) +{ + cli_action *q; + cli_action *a; + char *left = NULL; + char *right = NULL; + + if( split_pair( spec, &left, &right ) ) + { + snprintf( err, errsz, "expected NAME=VALUE: %s", spec ); + return( -1 ); + } + q = (cli_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + free( left ); + free( right ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + a = &q[*count]; + memset( a, 0, sizeof( *a ) ); + a->name = left; + a->edit.type = type; + a->edit.name = a->name; + + if( needs_file ) + { + if( cpu_melf_readall( right, &a->data, &a->edit.size, err, errsz ) ) + { + free( right ); + free( a->name ); + memset( a, 0, sizeof( *a ) ); + return( -1 ); + } + a->edit.data = a->data; + free( right ); + } + else + { + a->new_name = right; + a->edit.new_name = a->new_name; + } + ++*count; + return( 0 ); + +} /* End of append_edit_action() */ + + +static int +append_remove_action( cli_action **actions, size_t *count, + const char *name, char *err, size_t errsz ) +{ + cli_action *q; + cli_action *a; + + if( !name || !*name ) + { + snprintf( err, errsz, "empty section name" ); + return( -1 ); + } + q = (cli_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + a = &q[*count]; + memset( a, 0, sizeof( *a ) ); + a->name = strdup( name ); + if( !a->name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + a->edit.type = MCPU_OBJECT_EDIT_REMOVE; + a->edit.name = a->name; + ++*count; + return( 0 ); + +} /* End of append_remove_action() */ + + +static void +free_edit_actions( cli_action *actions, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + free( actions[i].name ); + free( actions[i].new_name ); + free( actions[i].data ); + } + free( actions ); + +} /* End of free_edit_actions() */ + + +static int +append_dump_action( dump_action **actions, size_t *count, + const char *spec, char *err, size_t errsz ) +{ + dump_action *q; + char *name = NULL; + char *path = NULL; + + if( split_pair( spec, &name, &path ) ) + { + snprintf( err, errsz, "expected NAME=FILE: %s", spec ); + return( -1 ); + } + q = (dump_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + free( name ); + free( path ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + q[*count].name = name; + q[*count].path = path; + ++*count; + return( 0 ); + +} /* End of append_dump_action() */ + + +static void +free_dump_actions( dump_action *actions, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + free( actions[i].name ); + free( actions[i].path ); + } + free( actions ); + +} /* End of free_dump_actions() */ + + +static int +write_output( const char *input, const char *output, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + struct stat st; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( cpu_melf_writeall( output, data, size, err, errsz ) ) + return( -1 ); + if( chmod( output, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + unlink( output ); + return( -1 ); + } + return( 0 ); + +} /* End of write_output() */ + + +static int +perform_dumps( const unsigned char *data, size_t size, + const dump_action *actions, size_t count, + char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + + if( mcpu_object_dump_section( data, size, actions[i].name, + &p, &n, err, errsz ) ) + return( -1 ); + if( cpu_melf_writeall( actions[i].path, p, n, err, errsz ) ) + { + free( p ); + return( -1 ); + } + free( p ); + } + return( 0 ); + +} /* End of perform_dumps() */ + + +static int +copy_archive( const char *input, const char *output, + const mcpu_object_edit_options *options, + char *err, size_t errsz ) +{ + mcpu_archive archive; + struct stat st; + size_t i; + int rc = -1; + + mcpu_archive_init( &archive ); + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( mcpu_archive_read( input, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + int changed = 0; + + if( mcpu_object_edit_data( archive.members[i].data, + archive.members[i].size, + options, &p, &n, &changed, err, errsz ) ) + goto out; + free( archive.members[i].data ); + archive.members[i].data = p; + archive.members[i].size = n; + (void)changed; + } + + if( mcpu_archive_write( output, &archive, archive.had_symbol_index, + err, errsz ) ) + goto out; + if( chmod( output, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + unlink( output ); + goto out; + } + rc = 0; + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of copy_archive() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argv[0]; + const char *input = NULL; + const char *output = NULL; + char **only = NULL; + size_t only_count = 0; + cli_action *actions = NULL; + size_t action_count = 0; + dump_action *dumps = NULL; + size_t dump_count = 0; + unsigned char *data = NULL; + size_t size = 0; + mcpu_object_edit_options options; + mcpu_object_edit_action *edit_actions = NULL; + char err[512]; + int i; + int rc = EXIT_FAILURE; + + err[0] = 0; + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, prog ); + rc = EXIT_SUCCESS; + goto out; + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE; + goto out; + } + if( !strcmp( arg, "-j" ) ) + { + if( ++i >= argc || append_string( &only, &only_count, argv[i] ) ) + { + snprintf( err, sizeof( err ), "option -j requires a section name" ); + goto bad_usage; + } + continue; + } + if( !strncmp( arg, "-j", 2 ) && arg[2] ) + { + if( append_string( &only, &only_count, arg + 2 ) ) + { + snprintf( err, sizeof( err ), "out of memory or empty section name" ); + goto bad_usage; + } + continue; + } + if( !strncmp( arg, "--only-section=", 15 ) ) + { + if( append_string( &only, &only_count, arg + 15 ) ) + { + snprintf( err, sizeof( err ), "--only-section requires a section name" ); + goto bad_usage; + } + continue; + } + if( !strcmp( arg, "-R" ) ) + { + if( ++i >= argc || append_remove_action( &actions, &action_count, + argv[i], err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "-R", 2 ) && arg[2] ) + { + if( append_remove_action( &actions, &action_count, arg + 2, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--remove-section=", 17 ) ) + { + if( append_remove_action( &actions, &action_count, arg + 17, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--dump-section=", 15 ) ) + { + if( append_dump_action( &dumps, &dump_count, arg + 15, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--add-section=", 14 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_ADD, + arg + 14, 1, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--update-section=", 17 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_UPDATE, + arg + 17, 1, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--rename-section=", 17 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_RENAME, + arg + 17, 0, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( arg[0] == '-' && arg[1] ) + { + snprintf( err, sizeof( err ), "unknown option: %s", arg ); + goto bad_usage; + } + if( !input ) + input = arg; + else if( !output ) + output = arg; + else + { + snprintf( err, sizeof( err ), "exactly INPUT and OUTPUT are required" ); + goto bad_usage; + } + } + + if( !input || !output ) + { + snprintf( err, sizeof( err ), "both INPUT and OUTPUT are required" ); + goto bad_usage; + } + if( !strcmp( input, output ) ) + { + snprintf( err, sizeof( err ), "INPUT and OUTPUT must be different files" ); + goto fail; + } + + if( action_count ) + { + size_t k; + + edit_actions = (mcpu_object_edit_action *)calloc( action_count, + sizeof( *edit_actions ) ); + if( !edit_actions ) + { + snprintf( err, sizeof( err ), "out of memory" ); + goto fail; + } + for( k = 0; k < action_count; ++k ) + edit_actions[k] = actions[k].edit; + } + memset( &options, 0, sizeof( options ) ); + options.only_sections = (const char *const *)only; + options.only_count = only_count; + options.actions = edit_actions; + options.action_count = action_count; + + if( cpu_melf_readall( input, &data, &size, err, sizeof( err ) ) ) + goto fail; + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + if( dump_count ) + { + snprintf( err, sizeof( err ), + "--dump-section is ambiguous for archive input" ); + goto fail; + } + if( !only_count && !action_count ) + { + if( write_output( input, output, data, size, err, sizeof( err ) ) ) + goto fail; + } + else if( copy_archive( input, output, &options, err, sizeof( err ) ) ) + goto fail; + } + else + { + unsigned char *result = NULL; + size_t result_size = 0; + int changed = 0; + + if( perform_dumps( data, size, dumps, dump_count, err, sizeof( err ) ) ) + goto fail; + if( mcpu_object_edit_data( data, size, &options, + &result, &result_size, &changed, + err, sizeof( err ) ) ) + goto fail; + if( write_output( input, output, result, result_size, + err, sizeof( err ) ) ) + { + free( result ); + goto fail; + } + free( result ); + (void)changed; + } + + rc = EXIT_SUCCESS; + goto out; + +bad_usage: + fprintf( stderr, "%s: %s\n", prog, err ); + usage( stderr, prog ); + goto out; + +fail: + fprintf( stderr, "%s: %s\n", prog, err ); + +out: + free( data ); + free( edit_actions ); + free_dump_actions( dumps, dump_count ); + free_edit_actions( actions, action_count ); + free_strings( only, only_count ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-objdump.c b/utils/mcpu-objdump.c new file mode 100644 index 0000000..f61435e --- /dev/null +++ b/utils/mcpu-objdump.c @@ -0,0 +1,1747 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct mcpu_objdump_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} mcpu_objdump_shdr; + + +typedef struct mcpu_objdump_file +{ + const char *name; + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t type; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + mcpu_objdump_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} mcpu_objdump_file; + + +typedef struct mcpu_objdump_options +{ + int disassemble; + int disassemble_all; + int relocations; + int full_contents; + int symbols; + int dynamic_symbols; + int section_headers; + char **sections; + size_t section_count; +} mcpu_objdump_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file(s)\n\n" + "Display and disassemble LibMCPU MELF objects, executables, shared objects and archives.\n\n" + "Options:\n" + " -d, --disassemble - disassemble executable sections\n" + " -D, --disassemble-all - disassemble all PROGBITS sections\n" + " -r, --reloc - display relocation entries\n" + " -s, --full-contents - display full contents of sections\n" + " -t, --syms - display .symtab symbols\n" + " -T, --dynamic-syms - display .dynsym symbols\n" + " -h, --section-headers - display section headers\n" + " -j, --section=NAME - restrict output to section NAME (repeatable)\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static void +objdump_file_free( mcpu_objdump_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of objdump_file_free() */ + + +static void +get_shdr( const unsigned char *p, int encoding, mcpu_objdump_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, encoding ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, encoding ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, encoding ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, encoding ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, encoding ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, encoding ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, encoding ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, encoding ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, encoding ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, encoding ); + +} /* End of get_shdr() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( NULL ); + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of string_at() */ + + +static int +objdump_file_open_data( const char *name, const unsigned char *data, size_t size, + mcpu_objdump_file *file, char *err, size_t errsz ) +{ + __mpu_uint64_t shoff; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->name = name; + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ) != + MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "unsupported MELF64 header size" ); + return( -1 ); + } + + file->type = cpu_melf_get16( data + MELF64_EHDR_E_TYPE_OFF, file->encoding ); + shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + file->shstrndx = cpu_melf_get16( data + MELF64_EHDR_E_SHSTRNDX_OFF, + file->encoding ); + + if( !file->shnum ) + return( 0 ); + if( shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( size, shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_objdump_shdr *)calloc( file->shnum, + sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + for( i = 0; i < file->shnum; ++i ) + get_shdr( data + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + + if( file->shstrndx >= file->shnum ) + { + snprintf( err, errsz, "invalid section-name string table index" ); + goto fail; + } + if( file->shdr[file->shstrndx].type != MELF_SHT_STRTAB || + file->shdr[file->shstrndx].size > SIZE_MAX || + !cpu_melf_span( size, file->shdr[file->shstrndx].offset, + file->shdr[file->shstrndx].size ) ) + { + snprintf( err, errsz, "malformed section-name string table" ); + goto fail; + } + file->shstr = data + (size_t)file->shdr[file->shstrndx].offset; + file->shstr_size = (size_t)file->shdr[file->shstrndx].size; + + for( i = 0; i < file->shnum; ++i ) + { + if( !string_at( file->shstr, file->shstr_size, file->shdr[i].name ) ) + { + snprintf( err, errsz, "section has invalid name offset" ); + goto fail; + } + if( file->shdr[i].type != MELF_SHT_NOBITS && + file->shdr[i].size && + !cpu_melf_span( size, file->shdr[i].offset, file->shdr[i].size ) ) + { + snprintf( err, errsz, "section payload is outside file" ); + goto fail; + } + } + + return( 0 ); + +fail: + objdump_file_free( file ); + return( -1 ); + +} /* End of objdump_file_open_data() */ + + +static const char * +section_name( const mcpu_objdump_file *file, size_t index ) +{ + if( !file || index >= file->shnum ) + return( "<invalid>" ); + return( string_at( file->shstr, file->shstr_size, file->shdr[index].name ) ); + +} /* End of section_name() */ + + +static int +section_selected( const mcpu_objdump_file *file, size_t index, + const mcpu_objdump_options *options ) +{ + const char *name; + size_t i; + + if( !options->section_count ) + return( 1 ); + name = section_name( file, index ); + for( i = 0; i < options->section_count; ++i ) + if( !strcmp( name, options->sections[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of section_selected() */ + + +static const char * +section_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_SHT_NULL: return( "NULL" ); + case MELF_SHT_PROGBITS: return( "PROGBITS" ); + case MELF_SHT_SYMTAB: return( "SYMTAB" ); + case MELF_SHT_STRTAB: return( "STRTAB" ); + case MELF_SHT_RELA: return( "RELA" ); + case MELF_SHT_DYNAMIC: return( "DYNAMIC" ); + case MELF_SHT_NOBITS: return( "NOBITS" ); + case MELF_SHT_DYNSYM: return( "DYNSYM" ); + case MELF_SHT_MCPU_TARGET: return( "MCPU_TARGET" ); + case MELF_SHT_MCPU_LIFECYCLE: return( "MCPU_LIFECYCLE" ); + default: return( "<unknown>" ); + } + +} /* End of section_type_name() */ + + +static const char * +symbol_bind_name( unsigned int bind ) +{ + switch( bind ) + { + case MELF_STB_LOCAL: return( "LOCAL" ); + case MELF_STB_GLOBAL: return( "GLOBAL" ); + case MELF_STB_WEAK: return( "WEAK" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_bind_name() */ + + +static const char * +symbol_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_STT_NOTYPE: return( "NOTYPE" ); + case MELF_STT_OBJECT: return( "OBJECT" ); + case MELF_STT_FUNC: return( "FUNC" ); + case MELF_STT_SECTION: return( "SECTION" ); + case MELF_STT_FILE: return( "FILE" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_type_name() */ + + +static const char * +relocation_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_R_MCPU_NONE: return( "R_MCPU_NONE" ); + case MELF_R_MCPU_CODE64: return( "R_MCPU_CODE64" ); + case MELF_R_MCPU_DATA64: return( "R_MCPU_DATA64" ); + case MELF_R_MCPU_PTR64_CODE: return( "R_MCPU_PTR64_CODE" ); + case MELF_R_MCPU_PTR64_DATA: return( "R_MCPU_PTR64_DATA" ); + case MELF_R_MCPU_CPTR64_DATA: return( "R_MCPU_CPTR64_DATA" ); + default: return( "<unknown>" ); + } + +} /* End of relocation_type_name() */ + + +static const char * +opcode_name( cpu_opcode opcode ) +{ + switch( opcode ) + { + case CPU_OP_NOP: return( "NOP" ); + case CPU_OP_HALT: return( "HALT" ); + case CPU_OP_END: return( "END" ); + case CPU_OP_EXIT: return( "EXIT" ); + case CPU_OP_SYSCALL: return( "SYSCALL" ); + case CPU_OP_SETF: return( "SETF" ); + case CPU_OP_CLRF: return( "CLRF" ); + case CPU_OP_CLRCPUF: return( "CLRCPUF" ); + case CPU_OP_CLRMPUF: return( "CLRMPUF" ); + case CPU_OP_MOV: return( "MOV" ); + case CPU_OP_CLR: return( "CLR" ); + case CPU_OP_SWAP: return( "SWAP" ); + case CPU_OP_BSWAP: return( "BSWAP" ); + case CPU_OP_LDI: return( "LDI" ); + case CPU_OP_LDR: return( "LDR" ); + case CPU_OP_LDC: return( "LDC" ); + case CPU_OP_LOAD: return( "LOAD" ); + case CPU_OP_STORE: return( "STORE" ); + case CPU_OP_LEA: return( "LEA" ); + case CPU_OP_LEASP: return( "LEASP" ); + case CPU_OP_LOADP: return( "LOADP" ); + case CPU_OP_STOREP: return( "STOREP" ); + case CPU_OP_PADD: return( "PADD" ); + case CPU_OP_PADDI: return( "PADDI" ); + case CPU_OP_PSUB: return( "PSUB" ); + case CPU_OP_PSUBI: return( "PSUBI" ); + case CPU_OP_PDIFF: return( "PDIFF" ); + case CPU_OP_BMOV: return( "BMOV" ); + case CPU_OP_BMOVI: return( "BMOVI" ); + case CPU_OP_IADD: return( "IADD" ); + case CPU_OP_IADC: return( "IADC" ); + case CPU_OP_ISUB: return( "ISUB" ); + case CPU_OP_ISBB: return( "ISBB" ); + case CPU_OP_IAND: return( "IAND" ); + case CPU_OP_IOR: return( "IOR" ); + case CPU_OP_IXOR: return( "IXOR" ); + case CPU_OP_INOT: return( "INOT" ); + case CPU_OP_INEG: return( "INEG" ); + case CPU_OP_ICMP: return( "ICMP" ); + case CPU_OP_ITEST: return( "ITEST" ); + case CPU_OP_ISHL: return( "ISHL" ); + case CPU_OP_ISHR: return( "ISHR" ); + case CPU_OP_ISAL: return( "ISAL" ); + case CPU_OP_ISAR: return( "ISAR" ); + case CPU_OP_IROL: return( "IROL" ); + case CPU_OP_IROR: return( "IROR" ); + case CPU_OP_IRCL: return( "IRCL" ); + case CPU_OP_IRCR: return( "IRCR" ); + case CPU_OP_ISHLN: return( "ISHLN" ); + case CPU_OP_ISHRN: return( "ISHRN" ); + case CPU_OP_ISALN: return( "ISALN" ); + case CPU_OP_ISARN: return( "ISARN" ); + case CPU_OP_IROLN: return( "IROLN" ); + case CPU_OP_IRORN: return( "IRORN" ); + case CPU_OP_IRCLN: return( "IRCLN" ); + case CPU_OP_IRCRN: return( "IRCRN" ); + case CPU_OP_IINC: return( "IINC" ); + case CPU_OP_IDEC: return( "IDEC" ); + case CPU_OP_IXCHG: return( "IXCHG" ); + case CPU_OP_ICPY: return( "ICPY" ); + case CPU_OP_ICVT: return( "ICVT" ); + case CPU_OP_IMUL: return( "IMUL" ); + case CPU_OP_ISMUL: return( "ISMUL" ); + case CPU_OP_IDIV: return( "IDIV" ); + case CPU_OP_ISDIV: return( "ISDIV" ); + case CPU_OP_IBITSLEN: return( "IBITSLEN" ); + case CPU_OP_RADD: return( "RADD" ); + case CPU_OP_RSUB: return( "RSUB" ); + case CPU_OP_RMUL: return( "RMUL" ); + case CPU_OP_RDIV: return( "RDIV" ); + case CPU_OP_RSQRT: return( "RSQRT" ); + case CPU_OP_RSIN: return( "RSIN" ); + case CPU_OP_RCOS: return( "RCOS" ); + case CPU_OP_RTAN: return( "RTAN" ); + case CPU_OP_RASIN: return( "RASIN" ); + case CPU_OP_RACOS: return( "RACOS" ); + case CPU_OP_RATAN: return( "RATAN" ); + case CPU_OP_RLOG: return( "RLOG" ); + case CPU_OP_RLOG10: return( "RLOG10" ); + case CPU_OP_RLOG2: return( "RLOG2" ); + case CPU_OP_REXP: return( "REXP" ); + case CPU_OP_RSINH: return( "RSINH" ); + case CPU_OP_RCOSH: return( "RCOSH" ); + case CPU_OP_RTANH: return( "RTANH" ); + case CPU_OP_RPOW: return( "RPOW" ); + case CPU_OP_RHYPOT: return( "RHYPOT" ); + case CPU_OP_RATAN2: return( "RATAN2" ); + case CPU_OP_RNEG: return( "RNEG" ); + case CPU_OP_RABS: return( "RABS" ); + case CPU_OP_RCMP: return( "RCMP" ); + case CPU_OP_RCPY: return( "RCPY" ); + case CPU_OP_RCVT: return( "RCVT" ); + case CPU_OP_RFLOOR: return( "RFLOOR" ); + case CPU_OP_RCEIL: return( "RCEIL" ); + case CPU_OP_RROUND: return( "RROUND" ); + case CPU_OP_RFREXP: return( "RFREXP" ); + case CPU_OP_RLDEXP: return( "RLDEXP" ); + case CPU_OP_RLOGB: return( "RLOGB" ); + case CPU_OP_RMODF: return( "RMODF" ); + case CPU_OP_RREMAIN: return( "RREMAIN" ); + case CPU_OP_RLOG1P: return( "RLOG1P" ); + case CPU_OP_REXPM1: return( "REXPM1" ); + case CPU_OP_RASINH: return( "RASINH" ); + case CPU_OP_RACOSH: return( "RACOSH" ); + case CPU_OP_RATANH: return( "RATANH" ); + case CPU_OP_ITOR: return( "ITOR" ); + case CPU_OP_UITOR: return( "UITOR" ); + case CPU_OP_RTOI: return( "RTOI" ); + case CPU_OP_RTOUI: return( "RTOUI" ); + case CPU_OP_RISNEG: return( "RISNEG" ); + case CPU_OP_RSIGN: return( "RSIGN" ); + case CPU_OP_RISIND: return( "RISIND" ); + case CPU_OP_RISNAN: return( "RISNAN" ); + case CPU_OP_RISNANMAX: return( "RISNANMAX" ); + case CPU_OP_RISNANMIN: return( "RISNANMIN" ); + case CPU_OP_RISINF: return( "RISINF" ); + case CPU_OP_RISSIGNULL: return( "RISSIGNULL" ); + case CPU_OP_RCONST: return( "RCONST" ); + case CPU_OP_CADD: return( "CADD" ); + case CPU_OP_CSUB: return( "CSUB" ); + case CPU_OP_CMUL: return( "CMUL" ); + case CPU_OP_CDIV: return( "CDIV" ); + case CPU_OP_CEXP: return( "CEXP" ); + case CPU_OP_CSIN: return( "CSIN" ); + case CPU_OP_CCOS: return( "CCOS" ); + case CPU_OP_CSINH: return( "CSINH" ); + case CPU_OP_CCOSH: return( "CCOSH" ); + case CPU_OP_CLOG: return( "CLOG" ); + case CPU_OP_CSQRT: return( "CSQRT" ); + case CPU_OP_CPOW: return( "CPOW" ); + case CPU_OP_CREAL: return( "CREAL" ); + case CPU_OP_CIMAG: return( "CIMAG" ); + case CPU_OP_CRTOC: return( "CRTOC" ); + case CPU_OP_CGEN: return( "CGEN" ); + case CPU_OP_CPOLAR: return( "CPOLAR" ); + case CPU_OP_CCONJ: return( "CCONJ" ); + case CPU_OP_CABS: return( "CABS" ); + case CPU_OP_CNORM: return( "CNORM" ); + case CPU_OP_CARG: return( "CARG" ); + case CPU_OP_CISEQ: return( "CISEQ" ); + case CPU_OP_CISNE: return( "CISNE" ); + case CPU_OP_CCPY: return( "CCPY" ); + case CPU_OP_CCVT: return( "CCVT" ); + case CPU_OP_JMP: return( "JMP" ); + case CPU_OP_JSET: return( "JSET" ); + case CPU_OP_JCLR: return( "JCLR" ); + case CPU_OP_JMPP: return( "JMPP" ); + case CPU_OP_CALL: return( "CALL" ); + case CPU_OP_RET: return( "RET" ); + case CPU_OP_CALLP: return( "CALLP" ); + case CPU_OP_PUSH: return( "PUSH" ); + case CPU_OP_POP: return( "POP" ); + case CPU_OP_LDSP: return( "LDSP" ); + case CPU_OP_STSP: return( "STSP" ); + case CPU_OP_ADJSP: return( "ADJSP" ); + case CPU_OP_PUSHFP: return( "PUSHFP" ); + case CPU_OP_SETFP: return( "SETFP" ); + case CPU_OP_RESTSP: return( "RESTSP" ); + case CPU_OP_POPFP: return( "POPFP" ); + case CPU_OP_LDFP: return( "LDFP" ); + case CPU_OP_STFP: return( "STFP" ); + case CPU_OP_LEAFP: return( "LEAFP" ); + case CPU_OP_SETSP: return( "SETSP" ); + default: return( NULL ); + } + +} /* End of opcode_name() */ + + +static const char * +rconst_name( unsigned int value ) +{ + switch( value ) + { + case CPU_RCONST_ZERO: return( "ZERO" ); + case CPU_RCONST_HALF: return( "HALF" ); + case CPU_RCONST_ONE: return( "ONE" ); + case CPU_RCONST_TWO: return( "TWO" ); + case CPU_RCONST_TEN: return( "TEN" ); + case CPU_RCONST_MTEN: return( "MTEN" ); + case CPU_RCONST_32: return( "32" ); + case CPU_RCONST_PI: return( "PI" ); + case CPU_RCONST_E: return( "E" ); + case CPU_RCONST_1_LN2: return( "1_LN2" ); + case CPU_RCONST_LN2: return( "LN2" ); + case CPU_RCONST_1_LN10: return( "1_LN10" ); + case CPU_RCONST_LN10: return( "LN10" ); + case CPU_RCONST_1_LG2: return( "1_LG2" ); + case CPU_RCONST_LG2: return( "LG2" ); + case CPU_RCONST_PI_2: return( "PI_2" ); + case CPU_RCONST_PI_3: return( "PI_3" ); + case CPU_RCONST_PI_4: return( "PI_4" ); + case CPU_RCONST_PI_5: return( "PI_5" ); + case CPU_RCONST_PI_6: return( "PI_6" ); + case CPU_RCONST_1_PI: return( "1_PI" ); + case CPU_RCONST_2_PI: return( "2_PI" ); + case CPU_RCONST_3_PI: return( "3_PI" ); + case CPU_RCONST_4_PI: return( "4_PI" ); + case CPU_RCONST_5_PI: return( "5_PI" ); + case CPU_RCONST_2PI: return( "2PI" ); + case CPU_RCONST_3PI: return( "3PI" ); + case CPU_RCONST_1_2PI: return( "1_2PI" ); + case CPU_RCONST_1_3PI: return( "1_3PI" ); + case CPU_RCONST_1_4PI: return( "1_4PI" ); + case CPU_RCONST_1_5PI: return( "1_5PI" ); + case CPU_RCONST_1_6PI: return( "1_6PI" ); + case CPU_RCONST_3PI_4: return( "3PI_4" ); + case CPU_RCONST_SQRTPI: return( "SQRTPI" ); + case CPU_RCONST_1_SQRTPI: return( "1_SQRTPI" ); + case CPU_RCONST_2_SQRTPI: return( "2_SQRTPI" ); + case CPU_RCONST_SQRT2: return( "SQRT2" ); + case CPU_RCONST_1_SQRT2: return( "1_SQRT2" ); + case CPU_RCONST_SQRT3: return( "SQRT3" ); + case CPU_RCONST_1_SQRT3: return( "1_SQRT3" ); + case CPU_RCONST_DEGREE: return( "DEGREE" ); + case CPU_RCONST_1_DEGREE: return( "1_DEGREE" ); + case CPU_RCONST_GOLDENRATIO: return( "GOLDENRATIO" ); + case CPU_RCONST_EULERGAMMA: return( "EULERGAMMA" ); + case CPU_RCONST_CATALAN: return( "CATALAN" ); + case CPU_RCONST_EPSILON: return( "EPSILON" ); + case CPU_RCONST_IND: return( "IND" ); + case CPU_RCONST_PNAN: return( "PNAN" ); + case CPU_RCONST_NNAN: return( "NNAN" ); + case CPU_RCONST_PNANMAX: return( "PNANMAX" ); + case CPU_RCONST_NNANMAX: return( "NNANMAX" ); + case CPU_RCONST_PNANMIN: return( "PNANMIN" ); + case CPU_RCONST_NNANMIN: return( "NNANMIN" ); + case CPU_RCONST_PINF: return( "PINF" ); + case CPU_RCONST_NINF: return( "NINF" ); + case CPU_RCONST_PREALMIN: return( "PREALMIN" ); + case CPU_RCONST_NREALMIN: return( "NREALMIN" ); + case CPU_RCONST_PREALMAX: return( "PREALMAX" ); + case CPU_RCONST_NREALMAX: return( "NREALMAX" ); + case CPU_RCONST_PZERO: return( "PZERO" ); + default: return( NULL ); + } + +} /* End of rconst_name() */ + + +static const char * +pswbit_name( unsigned int value ) +{ + switch( value ) + { + case CPU_PSWBIT_AF: return( "AF" ); + case CPU_PSWBIT_CF: return( "CF" ); + case CPU_PSWBIT_OF: return( "OF" ); + case CPU_PSWBIT_SF: return( "SF" ); + case CPU_PSWBIT_PF: return( "PF" ); + case CPU_PSWBIT_ZF: return( "ZF" ); + case CPU_PSWBIT_RF: return( "RF" ); + case CPU_PSWBIT_VF: return( "VF" ); + case CPU_PSWBIT_DOMF: return( "DOMF" ); + case CPU_PSWBIT_SNGF: return( "SNGF" ); + case CPU_PSWBIT_OVFF: return( "OVFF" ); + case CPU_PSWBIT_UDFF: return( "UDFF" ); + case CPU_PSWBIT_TLSF: return( "TLSF" ); + case CPU_PSWBIT_PLSF: return( "PLSF" ); + case CPU_PSWBIT_INDF: return( "INDF" ); + case CPU_PSWBIT_INXF: return( "INXF" ); + case CPU_PSWBIT_HF: return( "HF" ); + case CPU_PSWBIT_TF: return( "TF" ); + case CPU_PSWBIT_IF: return( "IF" ); + default: return( NULL ); + } + +} /* End of pswbit_name() */ + + +static int +opcode_complex_operand( cpu_opcode opcode, unsigned int operand ) +{ + switch( opcode ) + { + case CPU_OP_LDC: + return( operand == 0 ); + + case CPU_OP_CADD: case CPU_OP_CSUB: case CPU_OP_CMUL: + case CPU_OP_CDIV: case CPU_OP_CPOW: + return( operand < 3 ); + + case CPU_OP_CEXP: case CPU_OP_CSIN: case CPU_OP_CCOS: + case CPU_OP_CSINH: case CPU_OP_CCOSH: case CPU_OP_CLOG: + case CPU_OP_CSQRT: case CPU_OP_CCONJ: case CPU_OP_CCPY: + case CPU_OP_CCVT: + return( operand < 2 ); + + case CPU_OP_CREAL: case CPU_OP_CIMAG: case CPU_OP_CABS: + case CPU_OP_CNORM: case CPU_OP_CARG: + return( operand == 1 ); + + case CPU_OP_CRTOC: + return( operand == 0 ); + + case CPU_OP_CGEN: case CPU_OP_CPOLAR: + return( operand == 0 ); + + case CPU_OP_CISEQ: case CPU_OP_CISNE: + return( operand == 1 || operand == 2 ); + + default: + return( 0 ); + } + +} /* End of opcode_complex_operand() */ + + +static __mpu_uint64_t +symbol_section_offset( const mcpu_objdump_file *file, + const mcpu_objdump_shdr *section, + __mpu_uint64_t value ) +{ + if( file->type == MELF_ET_EXEC && + MELF_SHF_MCPU_HAS_SEG( section->flags ) ) + { + __mpu_uint64_t base = MELF_MCPU_PTR64_OFFSET( section->addr ); + __mpu_uint64_t pos = MELF_MCPU_PTR64_OFFSET( value ); + if( pos >= base ) + return( pos - base ); + } + return( value ); + +} /* End of symbol_section_offset() */ + + +static const char * +symbol_at( const mcpu_objdump_file *file, size_t section_index, + __mpu_uint64_t offset ) +{ + unsigned int pass; + + for( pass = 0; pass < 2; ++pass ) + { + __mpu_uint32_t wanted = pass == 0 ? MELF_SHT_SYMTAB : MELF_SHT_DYNSYM; + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != wanted ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->link >= file->shnum ) + continue; + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + continue; + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + const char *name; + + if( shndx != section_index ) + continue; + if( symbol_section_offset( file, &file->shdr[section_index], value ) != + offset ) + continue; + name = string_at( strtab, strsize, name_off ); + if( name && *name ) + return( name ); + } + } + } + + return( NULL ); + +} /* End of symbol_at() */ + + +static const char * +code_symbol_at( const mcpu_objdump_file *file, __mpu_uint64_t target ) +{ + unsigned int pass; + + if( file->type == MELF_ET_REL ) + return( NULL ); + + for( pass = 0; pass < 2; ++pass ) + { + __mpu_uint32_t wanted = pass == 0 ? MELF_SHT_SYMTAB : MELF_SHT_DYNSYM; + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != wanted ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->link >= file->shnum ) + continue; + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + continue; + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + __mpu_uint64_t code_value; + const char *name; + + if( shndx >= file->shnum || + !(file->shdr[shndx].flags & MELF_SHF_EXECINSTR) ) + continue; + + if( file->type == MELF_ET_EXEC ) + code_value = MELF_MCPU_PTR64_OFFSET( value ); + else if( MELF_SHF_MCPU_HAS_SEG( file->shdr[shndx].flags ) ) + code_value = MELF_MCPU_PTR64_OFFSET( file->shdr[shndx].addr ) + value; + else + code_value = value; + + if( code_value != target ) + continue; + name = string_at( strtab, strsize, name_off ); + if( name && *name ) + return( name ); + } + } + } + + return( NULL ); + +} /* End of code_symbol_at() */ + + +static void +print_section_headers( const mcpu_objdump_file *file, + const mcpu_objdump_options *options ) +{ + size_t i; + + printf( "Sections:\n" ); + printf( "Idx Name Size VMA " + "File off Algn Type\n" ); + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + + if( !section_selected( file, i, options ) ) + continue; + printf( "%3zu %-20s %016" PRIx64 " %016" PRIx64 " %016" PRIx64 + " %4" PRIu64 " %s", + i, section_name( file, i ), + (uint64_t)sh->size, (uint64_t)sh->addr, + (uint64_t)sh->offset, (uint64_t)sh->align, + section_type_name( sh->type ) ); + if( sh->flags & MELF_SHF_ALLOC ) printf( " ALLOC" ); + if( sh->flags & MELF_SHF_WRITE ) printf( " WRITE" ); + if( sh->flags & MELF_SHF_EXECINSTR ) printf( " CODE" ); + printf( "\n" ); + } + +} /* End of print_section_headers() */ + + +static int +print_symbol_table( const mcpu_objdump_file *file, __mpu_uint32_t table_type, + const mcpu_objdump_options *options, char *err, size_t errsz ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != table_type ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->size > SIZE_MAX || + !cpu_melf_span( file->size, sh->offset, sh->size ) || + sh->link >= file->shnum ) + { + snprintf( err, errsz, "malformed symbol table" ); + return( -1 ); + } + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "malformed symbol string table" ); + return( -1 ); + } + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + if( !printed ) + { + printf( "%sSYMBOL TABLE:\n", + table_type == MELF_SHT_DYNSYM ? "DYNAMIC " : "" ); + printf( "Value Size Bind Type Ndx Name\n" ); + printed = 1; + } + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + unsigned int bind = MELF64_ST_BIND( p[MELF64_SYM_ST_INFO_OFF] ); + unsigned int type = MELF64_ST_TYPE( p[MELF64_SYM_ST_INFO_OFF] ); + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint64_t ssize = + cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, file->encoding ); + const char *name = string_at( strtab, strsize, name_off ); + char ndx[32]; + + if( !name ) + { + snprintf( err, errsz, "symbol has invalid name offset" ); + return( -1 ); + } + if( shndx < file->shnum && !section_selected( file, shndx, options ) ) + continue; + + if( shndx == MELF_SHN_UNDEF ) + strcpy( ndx, "UND" ); + else if( shndx == MELF_SHN_ABS ) + strcpy( ndx, "ABS" ); + else if( shndx == MELF_SHN_COMMON ) + strcpy( ndx, "COM" ); + else + snprintf( ndx, sizeof( ndx ), "%u", (unsigned int)shndx ); + + printf( "%016" PRIx64 " %8" PRIu64 " %-6s %-8s %3s %s\n", + (uint64_t)value, (uint64_t)ssize, + symbol_bind_name( bind ), symbol_type_name( type ), + ndx, name ); + } + } + + return( 0 ); + +} /* End of print_symbol_table() */ + + +static int +relocation_symbol( const mcpu_objdump_file *file, + const mcpu_objdump_shdr *rela, __mpu_uint64_t symidx, + const char **name, char *err, size_t errsz ) +{ + const mcpu_objdump_shdr *symtab; + const mcpu_objdump_shdr *str; + const unsigned char *p; + const unsigned char *strtab; + size_t count; + __mpu_uint32_t name_off; + + *name = ""; + if( rela->link >= file->shnum ) + { + snprintf( err, errsz, "relocation section has invalid symbol-table link" ); + return( -1 ); + } + symtab = &file->shdr[rela->link]; + if( (symtab->type != MELF_SHT_SYMTAB && symtab->type != MELF_SHT_DYNSYM) || + symtab->entsize != MELF64_SYM_SIZE || + symtab->size % MELF64_SYM_SIZE || + symtab->link >= file->shnum ) + { + snprintf( err, errsz, "relocation section has malformed symbol table" ); + return( -1 ); + } + count = (size_t)(symtab->size / MELF64_SYM_SIZE); + if( symidx >= count ) + { + snprintf( err, errsz, "relocation has invalid symbol index" ); + return( -1 ); + } + str = &file->shdr[symtab->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "relocation symbol table has malformed strings" ); + return( -1 ); + } + p = file->data + (size_t)symtab->offset + + (size_t)symidx * MELF64_SYM_SIZE; + name_off = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + strtab = file->data + (size_t)str->offset; + *name = string_at( strtab, (size_t)str->size, name_off ); + if( !*name ) + { + snprintf( err, errsz, "relocation symbol has invalid name offset" ); + return( -1 ); + } + return( 0 ); + +} /* End of relocation_symbol() */ + + +static int +print_relocations( const mcpu_objdump_file *file, + const mcpu_objdump_options *options, + int skip_disassembled, char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA ) + continue; + if( sh->info >= file->shnum ) + { + snprintf( err, errsz, "relocation section has invalid target section" ); + return( -1 ); + } + if( !section_selected( file, sh->info, options ) ) + continue; + if( skip_disassembled ) + { + const mcpu_objdump_shdr *target = &file->shdr[sh->info]; + int eligible; + + if( options->disassemble_all ) + eligible = target->type == MELF_SHT_PROGBITS && target->size; + else + eligible = target->type == MELF_SHT_PROGBITS && target->size && + (target->flags & MELF_SHF_EXECINSTR) != 0; + if( eligible ) + continue; + } + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE || + sh->size > SIZE_MAX || + !cpu_melf_span( file->size, sh->offset, sh->size ) ) + { + snprintf( err, errsz, "malformed relocation section" ); + return( -1 ); + } + + count = (size_t)(sh->size / MELF64_RELA_SIZE); + printf( "RELOCATION RECORDS FOR [%s]:\n", + section_name( file, sh->info ) ); + printf( "OFFSET TYPE VALUE\n" ); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t offset = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + __mpu_uint64_t addbits = + cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, file->encoding ); + __mpu_int64_t addend; + __mpu_uint64_t symidx = MELF64_R_SYM( info ); + unsigned int rtype = MELF64_R_TYPE( info ); + const char *name; + + memcpy( &addend, &addbits, sizeof( addend ) ); + if( relocation_symbol( file, sh, symidx, &name, err, errsz ) ) + return( -1 ); + printf( "%016" PRIx64 " %-23s %s", + (uint64_t)offset, relocation_type_name( rtype ), name ); + if( addend > 0 ) + printf( "+0x%" PRIx64, (uint64_t)addend ); + else if( addend < 0 ) + { + __mpu_uint64_t mag = + (__mpu_uint64_t)(-(addend + 1)) + __mpu_UINT64_C( 1 ); + printf( "-0x%" PRIx64, (uint64_t)mag ); + } + printf( "\n" ); + } + printf( "\n" ); + } + + return( 0 ); + +} /* End of print_relocations() */ + + +static void +print_contents( const mcpu_objdump_file *file, + const mcpu_objdump_options *options ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t off; + + if( !section_selected( file, i, options ) || + sh->type == MELF_SHT_NULL || sh->type == MELF_SHT_NOBITS || + !sh->size ) + continue; + + printf( "Contents of section %s:\n", section_name( file, i ) ); + for( off = 0; off < (size_t)sh->size; off += 16 ) + { + size_t k; + size_t n = (size_t)sh->size - off; + __mpu_uint64_t addr = sh->addr + off; + + if( n > 16 ) n = 16; + printf( " %016" PRIx64 " ", (uint64_t)addr ); + for( k = 0; k < 16; ++k ) + { + if( k < n ) + printf( "%02x", file->data[(size_t)sh->offset + off + k] ); + else + printf( " " ); + if( (k & 3U) == 3U ) putchar( ' ' ); + } + putchar( ' ' ); + for( k = 0; k < n; ++k ) + { + unsigned char ch = file->data[(size_t)sh->offset + off + k]; + putchar( isprint( ch ) ? ch : '.' ); + } + putchar( '\n' ); + } + printf( "\n" ); + } + +} /* End of print_contents() */ + + +static const char * +instruction_relocation_symbol( const mcpu_objdump_file *file, + size_t section_index, + __mpu_uint64_t first, + __mpu_uint64_t last ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA || sh->info != section_index || + sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE ) + continue; + + count = (size_t)(sh->size / MELF64_RELA_SIZE); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t off = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + const char *name; + char err[128]; + + if( off < first || off >= last ) + continue; + if( !relocation_symbol( file, sh, MELF64_R_SYM( info ), + &name, err, sizeof( err ) ) && + name && *name ) + return( name ); + } + } + + return( NULL ); + +} /* End of instruction_relocation_symbol() */ + + +static void +format_operand( const mcpu_objdump_file *file, size_t section_index, + __mpu_uint64_t instruction_offset, + __mpu_uint64_t instruction_length, + cpu_opcode opcode, unsigned int index, + const cpu_operand *operand, char *buf, size_t bufsz ) +{ + const char *name; + const char *relname = + instruction_relocation_symbol( file, section_index, instruction_offset, + instruction_offset + instruction_length ); + + switch( operand->type ) + { + case CPU_OPERAND_REGISTER: + snprintf( buf, bufsz, "%c%u@0x%05" PRIx32, + opcode_complex_operand( opcode, index ) ? 'c' : 'r', + operand->value.reg.bits, + (uint32_t)operand->value.reg.address ); + break; + + case CPU_OPERAND_IMM64: + if( opcode == CPU_OP_LDI ) + { + __mpu_int64_t v; + memcpy( &v, &operand->value.imm64, sizeof( v ) ); + snprintf( buf, bufsz, "%" PRId64, (int64_t)v ); + } + else if( opcode == CPU_OP_BMOVI ) + snprintf( buf, bufsz, "%" PRIu64, + (uint64_t)operand->value.imm64 ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.imm64 ); + break; + + case CPU_OPERAND_ADDR64: + if( relname ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.addr64, relname ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.addr64 ); + break; + + case CPU_OPERAND_CODE64: + name = relname ? relname : code_symbol_at( file, operand->value.code64 ); + if( name ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.code64, name ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.code64 ); + break; + + case CPU_OPERAND_OFF64: + snprintf( buf, bufsz, "%" PRId64, (int64_t)operand->value.off64 ); + break; + + case CPU_OPERAND_CONST8: + name = rconst_name( operand->value.const8 ); + if( name ) snprintf( buf, bufsz, "%s", name ); + else snprintf( buf, bufsz, "0x%02x", + (unsigned int)operand->value.const8 ); + break; + + case CPU_OPERAND_PSWBIT8: + name = pswbit_name( operand->value.pswbit8 ); + if( name ) snprintf( buf, bufsz, "%s", name ); + else snprintf( buf, bufsz, "%u", + (unsigned int)operand->value.pswbit8 ); + break; + + case CPU_OPERAND_PTR64: + if( relname ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.ptr64, relname ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.ptr64 ); + break; + + default: + *buf = 0; + break; + } + +} /* End of format_operand() */ + + +static int +print_instruction_relocations( const mcpu_objdump_file *file, + size_t section_index, __mpu_uint64_t first, + __mpu_uint64_t last, char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA || sh->info != section_index ) + continue; + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE ) + continue; + count = (size_t)(sh->size / MELF64_RELA_SIZE); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t off = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + __mpu_uint64_t addbits = + cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, file->encoding ); + __mpu_int64_t addend; + const char *name; + + if( off < first || off >= last ) + continue; + memcpy( &addend, &addbits, sizeof( addend ) ); + if( relocation_symbol( file, sh, MELF64_R_SYM( info ), + &name, err, errsz ) ) + return( -1 ); + printf( "\t\t%016" PRIx64 ": %-23s %s", + (uint64_t)off, + relocation_type_name( MELF64_R_TYPE( info ) ), name ); + if( addend > 0 ) + printf( "+0x%" PRIx64, (uint64_t)addend ); + else if( addend < 0 ) + { + __mpu_uint64_t mag = + (__mpu_uint64_t)(-(addend + 1)) + __mpu_UINT64_C( 1 ); + printf( "-0x%" PRIx64, (uint64_t)mag ); + } + putchar( '\n' ); + } + } + + return( 0 ); + +} /* End of print_instruction_relocations() */ + + +static int +disassemble_section( const mcpu_objdump_file *file, size_t section_index, + const mcpu_objdump_options *options, + char *err, size_t errsz ) +{ + const mcpu_objdump_shdr *sh = &file->shdr[section_index]; + const unsigned char *program = file->data + (size_t)sh->offset; + __mpu_uint64_t off = 0; + + printf( "Disassembly of section %s:\n\n", section_name( file, section_index ) ); + + while( off < sh->size ) + { + cpu_instruction insn; + const char *label = symbol_at( file, section_index, off ); + int rc; + + if( label ) + printf( "%016" PRIx64 " <%s>:\n", + (uint64_t)(sh->addr + off), label ); + + rc = __cpu_decode_at( program, sh->size, off, &insn ); + if( rc == CPU_EXEC_SUCCESS ) + { + const char *mn = opcode_name( insn.opcode ); + unsigned int k; + char operand[CPU_INSTRUCTION_MAX_OPERANDS][128]; + size_t b; + + if( !mn || !insn.length || insn.length > sh->size - off ) + rc = CPU_EXEC_DECODE_FAULT; + else + { + printf( " %016" PRIx64 ": ", (uint64_t)(sh->addr + off) ); + for( b = 0; b < 16; ++b ) + { + if( b < insn.length ) + printf( "%02x ", program[(size_t)off + b] ); + else + printf( " " ); + } + printf( " %-10s", mn ); + for( k = 0; k < CPU_INSTRUCTION_MAX_OPERANDS; ++k ) + { + if( insn.operand[k].type == CPU_OPERAND_NONE ) + break; + format_operand( file, section_index, off, insn.length, + insn.opcode, k, &insn.operand[k], + operand[k], sizeof( operand[k] ) ); + printf( "%s%s", k ? ", " : " ", operand[k] ); + } + putchar( '\n' ); + + if( options->relocations && + print_instruction_relocations( file, section_index, off, + off + insn.length, err, errsz ) ) + return( -1 ); + off += insn.length; + continue; + } + } + + printf( " %016" PRIx64 ": %02x", + (uint64_t)(sh->addr + off), program[(size_t)off] ); + printf( "%*s.byte 0x%02x\n", 47, "", + program[(size_t)off] ); + ++off; + } + printf( "\n" ); + return( 0 ); + +} /* End of disassemble_section() */ + + +static int +print_disassembly( const mcpu_objdump_file *file, + const mcpu_objdump_options *options, + char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + int eligible; + + if( !section_selected( file, i, options ) || !sh->size || + sh->type == MELF_SHT_NOBITS ) + continue; + + if( options->disassemble_all ) + eligible = sh->type == MELF_SHT_PROGBITS; + else + eligible = sh->type == MELF_SHT_PROGBITS && + (sh->flags & MELF_SHF_EXECINSTR) != 0; + if( !eligible ) + continue; + + if( disassemble_section( file, i, options, err, errsz ) ) + return( -1 ); + } + + return( 0 ); + +} /* End of print_disassembly() */ + + +static int +process_melf_data( const char *name, const unsigned char *data, size_t size, + const mcpu_objdump_options *options ) +{ + mcpu_objdump_file file; + char err[256]; + + if( objdump_file_open_data( name, data, size, &file, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s: %s\n", name, err ); + return( 1 ); + } + + printf( "\n%s: file format melf64-mcpu\n\n", name ); + + if( options->section_headers ) + print_section_headers( &file, options ); + + if( options->symbols ) + { + if( options->section_headers ) putchar( '\n' ); + if( print_symbol_table( &file, MELF_SHT_SYMTAB, options, + err, sizeof( err ) ) ) + goto fail; + } + if( options->dynamic_symbols ) + { + if( options->section_headers || options->symbols ) putchar( '\n' ); + if( print_symbol_table( &file, MELF_SHT_DYNSYM, options, + err, sizeof( err ) ) ) + goto fail; + } + + if( options->full_contents ) + print_contents( &file, options ); + + if( options->relocations ) + { + if( print_relocations( &file, options, + options->disassemble || options->disassemble_all, + err, sizeof( err ) ) ) + goto fail; + } + + if( options->disassemble || options->disassemble_all ) + { + if( print_disassembly( &file, options, err, sizeof( err ) ) ) + goto fail; + } + + objdump_file_free( &file ); + return( 0 ); + +fail: + fprintf( stderr, "mcpu-objdump: %s: %s\n", name, err ); + objdump_file_free( &file ); + return( 1 ); + +} /* End of process_melf_data() */ + + +static int +process_archive( const char *path, const mcpu_objdump_options *options ) +{ + mcpu_archive archive; + char err[256]; + size_t i; + int status = 0; + + mcpu_archive_init( &archive ); + if( mcpu_archive_read( path, &archive, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s: %s\n", path, err ); + return( 1 ); + } + + for( i = 0; i < archive.member_count; ++i ) + { + char *display; + size_t n = strlen( path ) + strlen( archive.members[i].name ) + 3; + + display = (char *)malloc( n ); + if( !display ) + { + fprintf( stderr, "mcpu-objdump: %s: out of memory\n", path ); + status = 1; + break; + } + snprintf( display, n, "%s(%s)", path, archive.members[i].name ); + if( process_melf_data( display, archive.members[i].data, + archive.members[i].size, options ) ) + status = 1; + free( display ); + } + + mcpu_archive_free( &archive ); + return( status ); + +} /* End of process_archive() */ + + +static int +process_path( const char *path, const mcpu_objdump_options *options ) +{ + unsigned char *data = NULL; + size_t size = 0; + char err[256]; + int status; + + if( cpu_melf_readall( path, &data, &size, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s\n", err ); + return( 1 ); + } + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + free( data ); + return( process_archive( path, options ) ); + } + + status = process_melf_data( path, data, size, options ); + free( data ); + return( status ); + +} /* End of process_path() */ + + +static int +add_section( mcpu_objdump_options *options, const char *name ) +{ + char **q; + char *copy; + + if( !name || !*name ) + return( -1 ); + q = (char **)realloc( options->sections, + (options->section_count + 1) * sizeof( *q ) ); + if( !q ) + return( -1 ); + copy = strdup( name ); + if( !copy ) + return( -1 ); + options->sections = q; + options->sections[options->section_count++] = copy; + return( 0 ); + +} /* End of add_section() */ + + +static void +free_options( mcpu_objdump_options *options ) +{ + size_t i; + + for( i = 0; i < options->section_count; ++i ) + free( options->sections[i] ); + free( options->sections ); + +} /* End of free_options() */ + + +static int +parse_short_options( const char *arg, mcpu_objdump_options *options, + const char **section_arg ) +{ + const char *p = arg + 1; + + while( *p ) + { + switch( *p ) + { + case 'd': options->disassemble = 1; ++p; break; + case 'D': options->disassemble_all = 1; ++p; break; + case 'r': options->relocations = 1; ++p; break; + case 's': options->full_contents = 1; ++p; break; + case 't': options->symbols = 1; ++p; break; + case 'T': options->dynamic_symbols = 1; ++p; break; + case 'h': options->section_headers = 1; ++p; break; + + case 'j': + ++p; + if( *p ) + { + *section_arg = p; + return( 1 ); + } + return( 2 ); + + default: + return( -1 ); + } + } + return( 0 ); + +} /* End of parse_short_options() */ + + +int +main( int argc, char **argv ) +{ + mcpu_objdump_options options; + int first_file = -1; + int status = 0; + int i; + + memset( &options, 0, sizeof( options ) ); + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--" ) ) + { + first_file = i + 1; + break; + } + if( arg[0] != '-' || !arg[1] ) + { + first_file = i; + break; + } + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free_options( &options ); + return( 0 ); + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free_options( &options ); + return( rc == 0 ? 0 : 1 ); + } + if( !strcmp( arg, "--disassemble" ) ) options.disassemble = 1; + else if( !strcmp( arg, "--disassemble-all" ) ) options.disassemble_all = 1; + else if( !strcmp( arg, "--reloc" ) ) options.relocations = 1; + else if( !strcmp( arg, "--full-contents" ) ) options.full_contents = 1; + else if( !strcmp( arg, "--syms" ) ) options.symbols = 1; + else if( !strcmp( arg, "--dynamic-syms" ) ) options.dynamic_symbols = 1; + else if( !strcmp( arg, "--section-headers" ) ) options.section_headers = 1; + else if( !strncmp( arg, "--section=", 10 ) ) + { + if( add_section( &options, arg + 10 ) ) + { + fprintf( stderr, "mcpu-objdump: out of memory or empty section name\n" ); + free_options( &options ); + return( 1 ); + } + } + else if( !strcmp( arg, "-j" ) || !strcmp( arg, "--section" ) ) + { + if( i + 1 >= argc || add_section( &options, argv[++i] ) ) + { + fprintf( stderr, "mcpu-objdump: option %s requires a section name\n", + arg ); + free_options( &options ); + return( 1 ); + } + } + else if( arg[0] == '-' && arg[1] != '-' ) + { + const char *section_arg = NULL; + int rc = parse_short_options( arg, &options, §ion_arg ); + + if( rc < 0 ) + { + fprintf( stderr, "mcpu-objdump: unknown option: %s\n", arg ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + if( rc == 1 ) + { + if( add_section( &options, section_arg ) ) + { + fprintf( stderr, "mcpu-objdump: out of memory or empty section name\n" ); + free_options( &options ); + return( 1 ); + } + } + else if( rc == 2 ) + { + if( i + 1 >= argc || add_section( &options, argv[++i] ) ) + { + fprintf( stderr, "mcpu-objdump: option -j requires a section name\n" ); + free_options( &options ); + return( 1 ); + } + } + } + else + { + fprintf( stderr, "mcpu-objdump: unknown option: %s\n", arg ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + } + + if( first_file < 0 || first_file >= argc ) + { + fprintf( stderr, "mcpu-objdump: no input file\n" ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + if( !options.disassemble && !options.disassemble_all && + !options.relocations && !options.full_contents && + !options.symbols && !options.dynamic_symbols && + !options.section_headers ) + { + fprintf( stderr, "mcpu-objdump: at least one display option is required\n" ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + + for( i = first_file; i < argc; ++i ) + { + if( process_path( argv[i], &options ) ) + status = 1; + } + + free_options( &options ); + return( status ); + +} /* End of main() */ diff --git a/utils/mcpu-object-edit.c b/utils/mcpu-object-edit.c new file mode 100644 index 0000000..ac0db3c --- /dev/null +++ b/utils/mcpu-object-edit.c @@ -0,0 +1,1195 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" + + +typedef struct mcpu_edit_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} mcpu_edit_shdr; + + +typedef struct mcpu_edit_file +{ + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t type; + __mpu_uint64_t phoff; + __mpu_uint16_t phnum; + __mpu_uint64_t shoff; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + mcpu_edit_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} mcpu_edit_file; + + +typedef struct mcpu_edit_section +{ + mcpu_edit_shdr shdr; + char *name; + const unsigned char *data; + unsigned char *owned_data; + size_t data_size; + __mpu_uint16_t old_index; + int is_new; + int removed; +} mcpu_edit_section; + + +static void +get_shdr( const unsigned char *p, int data, mcpu_edit_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +put_shdr( unsigned char *p, int data, const mcpu_edit_shdr *sh ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, sh->name, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, sh->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, sh->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, sh->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, sh->offset, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, sh->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, sh->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, sh->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, sh->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, sh->entsize, data ); + +} /* End of put_shdr() */ + + +static int +shdr_is_zero( const mcpu_edit_shdr *sh ) +{ + return( sh->name == 0 && sh->type == MELF_SHT_NULL && sh->flags == 0 && + sh->addr == 0 && sh->offset == 0 && sh->size == 0 && + sh->link == 0 && sh->info == 0 && sh->align == 0 && + sh->entsize == 0 ); + +} /* End of shdr_is_zero() */ + + +static const char * +name_at( const mcpu_edit_file *file, __mpu_uint32_t offset ) +{ + const unsigned char *p; + + if( offset >= file->shstr_size ) + return( NULL ); + p = file->shstr + offset; + if( !memchr( p, 0, file->shstr_size - offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of name_at() */ + + +static void +edit_file_free( mcpu_edit_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of edit_file_free() */ + + +static int +edit_file_open( const unsigned char *data, size_t size, mcpu_edit_file *file, + char *err, size_t errsz ) +{ + __mpu_uint16_t ehsize; + __mpu_uint16_t phentsize; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( data[MELF_EI_VERSION] != MELF_EV_CURRENT || + data[MELF_EI_OSABI] != MELF_OSABI_MCPU || + cpu_melf_get16( data + MELF64_EHDR_E_MACHINE_OFF, file->encoding ) != + MELF_EM_MCPU || + cpu_melf_get32( data + MELF64_EHDR_E_VERSION_OFF, file->encoding ) != + MELF_EV_CURRENT ) + { + snprintf( err, errsz, "unsupported MELF header" ); + return( -1 ); + } + + ehsize = cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ); + phentsize = cpu_melf_get16( data + MELF64_EHDR_E_PHENTSIZE_OFF, + file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->type = cpu_melf_get16( data + MELF64_EHDR_E_TYPE_OFF, + file->encoding ); + file->phoff = cpu_melf_get64( data + MELF64_EHDR_E_PHOFF_OFF, + file->encoding ); + file->phnum = cpu_melf_get16( data + MELF64_EHDR_E_PHNUM_OFF, + file->encoding ); + file->shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + file->shstrndx = cpu_melf_get16( data + MELF64_EHDR_E_SHSTRNDX_OFF, + file->encoding ); + + if( ehsize != MELF64_EHDR_SIZE || + ( file->type != MELF_ET_REL && file->type != MELF_ET_EXEC && + file->type != MELF_ET_DYN ) ) + { + snprintf( err, errsz, "unsupported MELF64 file type/header size" ); + return( -1 ); + } + + if( file->phnum ) + { + if( phentsize != MELF64_PHDR_SIZE || + !cpu_melf_span( size, file->phoff, + (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed program-header table" ); + return( -1 ); + } + } + else if( phentsize != 0 && file->type == MELF_ET_REL ) + { + snprintf( err, errsz, "ET_REL has unexpected program-header entry size" ); + return( -1 ); + } + + if( !file->shnum || shentsize != MELF64_SHDR_SIZE || + file->shstrndx == MELF_SHN_UNDEF || file->shstrndx >= file->shnum || + !cpu_melf_span( size, file->shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_edit_shdr *)calloc( file->shnum, sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + { + get_shdr( data + (size_t)file->shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size && + !cpu_melf_span( size, file->shdr[i].offset, file->shdr[i].size ) ) + { + snprintf( err, errsz, "section %zu has an invalid file range", i ); + edit_file_free( file ); + return( -1 ); + } + } + + if( !shdr_is_zero( &file->shdr[0] ) || + file->shdr[file->shstrndx].type != MELF_SHT_STRTAB || + !file->shdr[file->shstrndx].size || + file->shdr[file->shstrndx].size > SIZE_MAX ) + { + snprintf( err, errsz, "malformed MELF section-name table" ); + edit_file_free( file ); + return( -1 ); + } + + file->shstr = data + (size_t)file->shdr[file->shstrndx].offset; + file->shstr_size = (size_t)file->shdr[file->shstrndx].size; + for( i = 1; i < file->shnum; ++i ) + if( !name_at( file, file->shdr[i].name ) ) + { + snprintf( err, errsz, "section %zu has an invalid name", i ); + edit_file_free( file ); + return( -1 ); + } + + return( 0 ); + +} /* End of edit_file_open() */ + + +static int +copy_original( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + unsigned char *p = (unsigned char *)malloc( size ? size : 1 ); + + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + if( size ) + memcpy( p, data, size ); + *out = p; + *out_size = size; + return( 0 ); + +} /* End of copy_original() */ + + +static void +sections_free( mcpu_edit_section *sections, size_t count ) +{ + size_t i; + + if( !sections ) + return; + for( i = 0; i < count; ++i ) + { + free( sections[i].name ); + free( sections[i].owned_data ); + } + free( sections ); + +} /* End of sections_free() */ + + +static int +sections_from_file( const mcpu_edit_file *file, + mcpu_edit_section **result, size_t *count, + char *err, size_t errsz ) +{ + mcpu_edit_section *sections; + size_t i; + + sections = (mcpu_edit_section *)calloc( file->shnum, sizeof( *sections ) ); + if( !sections ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + { + const char *name = i ? name_at( file, file->shdr[i].name ) : ""; + + sections[i].shdr = file->shdr[i]; + sections[i].name = strdup( name ? name : "" ); + sections[i].old_index = (__mpu_uint16_t)i; + if( !sections[i].name ) + { + snprintf( err, errsz, "out of memory" ); + sections_free( sections, file->shnum ); + return( -1 ); + } + if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size ) + { + sections[i].data = file->data + (size_t)file->shdr[i].offset; + sections[i].data_size = (size_t)file->shdr[i].size; + } + } + + *result = sections; + *count = file->shnum; + return( 0 ); + +} /* End of sections_from_file() */ + + +static size_t +find_section( const mcpu_edit_section *sections, size_t count, + const char *name ) +{ + size_t i; + + for( i = 1; i < count; ++i ) + if( !sections[i].removed && !strcmp( sections[i].name, name ) ) + return( i ); + return( SIZE_MAX ); + +} /* End of find_section() */ + + +static int +name_selected( const char *name, const char *const *only, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + if( !strcmp( name, only[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of name_selected() */ + + + +static int +reserved_section_name( const char *name ) +{ + static const char *const reserved[] = + { + MELF_SEC_INIT, MELF_SEC_TEXT, MELF_SEC_FINI, + MELF_SEC_DATA, MELF_SEC_BSS, MELF_SEC_CONTROL, + MELF_SEC_INPUT, MELF_SEC_OUTPUT, MELF_SEC_TRACE, + MELF_SEC_SYMTAB, MELF_SEC_STRTAB, MELF_SEC_SHSTRTAB, + MELF_SEC_DYNSYM, MELF_SEC_DYNSTR, MELF_SEC_DYNAMIC, + MELF_SEC_MCPU_TARGET, MELF_SEC_MCPU_LIFECYCLE, + MELF_SEC_RELA_INIT, MELF_SEC_RELA_TEXT, MELF_SEC_RELA_FINI, + MELF_SEC_RELA_DATA, MELF_SEC_RELA_CONTROL, MELF_SEC_RELA_INPUT, + MELF_SEC_RELA_OUTPUT, MELF_SEC_RELA_TRACE + }; + size_t i; + + if( !name || !*name ) + return( 1 ); + if( !strncmp( name, ".rela.", 6 ) ) + return( 1 ); + for( i = 0; i < sizeof( reserved ) / sizeof( reserved[0] ); ++i ) + if( !strcmp( name, reserved[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of reserved_section_name() */ + +static int +section_runtime_critical( const mcpu_edit_file *file, + const mcpu_edit_section *section ) +{ + const mcpu_edit_shdr *sh = §ion->shdr; + + if( sh->flags & MELF_SHF_ALLOC ) + return( 1 ); + if( section->old_index == file->shstrndx ) + return( 1 ); + if( sh->type == MELF_SHT_DYNAMIC || sh->type == MELF_SHT_DYNSYM || + sh->type == MELF_SHT_RELA || sh->type == MELF_SHT_MCPU_TARGET || + sh->type == MELF_SHT_MCPU_LIFECYCLE ) + return( 1 ); + if( file->type == MELF_ET_REL && + ( sh->type == MELF_SHT_SYMTAB || sh->type == MELF_SHT_STRTAB ) ) + return( 1 ); + return( 0 ); + +} /* End of section_runtime_critical() */ + + +static int +section_user_editable( const mcpu_edit_file *file, + const mcpu_edit_section *section ) +{ + if( section_runtime_critical( file, section ) ) + return( 0 ); + return( section->shdr.type == MELF_SHT_PROGBITS && + !( section->shdr.flags & MELF_SHF_ALLOC ) ); + +} /* End of section_user_editable() */ + + +static int +section_link_references( const mcpu_edit_section *sections, size_t count, + size_t target ) +{ + size_t i; + + for( i = 1; i < count; ++i ) + { + if( sections[i].removed || sections[i].is_new ) + continue; + if( sections[i].shdr.link == target ) + return( 1 ); + if( sections[i].shdr.type == MELF_SHT_RELA && + sections[i].shdr.info == target ) + return( 1 ); + } + return( 0 ); + +} /* End of section_link_references() */ + + +static int +safe_remove_section( const mcpu_edit_file *file, + mcpu_edit_section *sections, size_t count, + size_t index, const char *reason, + char *err, size_t errsz ) +{ + if( index == 0 || index >= count || sections[index].removed ) + return( 0 ); + if( section_runtime_critical( file, §ions[index] ) ) + { + snprintf( err, errsz, "%s cannot remove section %s", + reason, sections[index].name ); + return( -1 ); + } + if( section_link_references( sections, count, index ) ) + { + snprintf( err, errsz, "%s cannot remove referenced section %s", + reason, sections[index].name ); + return( -1 ); + } + sections[index].removed = 1; + return( 0 ); + +} /* End of safe_remove_section() */ + + +static int +replace_section_data( mcpu_edit_section *section, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + unsigned char *p = NULL; + + if( size ) + { + p = (unsigned char *)malloc( size ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + memcpy( p, data, size ); + } + free( section->owned_data ); + section->owned_data = p; + section->data = p; + section->data_size = size; + section->shdr.size = size; + return( 0 ); + +} /* End of replace_section_data() */ + + +static int +add_section( mcpu_edit_section **psections, size_t *pcount, + const char *name, const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + mcpu_edit_section *sections = *psections; + size_t count = *pcount; + mcpu_edit_section *q; + mcpu_edit_section *s; + + if( !name || !*name ) + { + snprintf( err, errsz, "section name must not be empty" ); + return( -1 ); + } + if( reserved_section_name( name ) ) + { + snprintf( err, errsz, "cannot add reserved section name %s", name ); + return( -1 ); + } + if( count == UINT16_MAX ) + { + snprintf( err, errsz, "too many sections" ); + return( -1 ); + } + q = (mcpu_edit_section *)realloc( sections, + (count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + sections = q; + memset( §ions[count], 0, sizeof( sections[count] ) ); + s = §ions[count]; + s->name = strdup( name ); + if( !s->name ) + { + snprintf( err, errsz, "out of memory" ); + *psections = sections; + return( -1 ); + } + s->is_new = 1; + s->old_index = MELF_SHN_UNDEF; + s->shdr.type = MELF_SHT_PROGBITS; + s->shdr.align = 1; + if( replace_section_data( s, data, size, err, errsz ) ) + { + free( s->name ); + memset( s, 0, sizeof( *s ) ); + *psections = sections; + return( -1 ); + } + + *psections = sections; + *pcount = count + 1; + return( 0 ); + +} /* End of add_section() */ + + +static int +apply_options( const mcpu_edit_file *file, + mcpu_edit_section **psections, size_t *pcount, + const mcpu_object_edit_options *options, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_section *sections = *psections; + size_t count = *pcount; + size_t i; + + if( options->only_count ) + { + int any = 0; + + /* Mark the whole filter first so dependencies between sections that are + both being discarded do not make the result depend on section order. */ + for( i = 1; i < count; ++i ) + if( !name_selected( sections[i].name, options->only_sections, + options->only_count ) ) + { + if( section_runtime_critical( file, §ions[i] ) ) + { + snprintf( err, errsz, "--only-section cannot remove section %s", + sections[i].name ); + return( -1 ); + } + sections[i].removed = 1; + any = 1; + } + + for( i = 1; i < count; ++i ) + if( sections[i].removed && section_link_references( sections, count, i ) ) + { + snprintf( err, errsz, + "--only-section cannot remove referenced section %s", + sections[i].name ); + return( -1 ); + } + if( any ) + *changed = 1; + } + + for( i = 0; i < options->action_count; ++i ) + { + const mcpu_object_edit_action *a = &options->actions[i]; + size_t index; + + if( a->type == MCPU_OBJECT_EDIT_ADD ) + { + if( find_section( sections, count, a->name ) != SIZE_MAX ) + { + snprintf( err, errsz, "section %s already exists", a->name ); + return( -1 ); + } + if( add_section( §ions, &count, a->name, a->data, a->size, + err, errsz ) ) + return( -1 ); + *psections = sections; + *pcount = count; + *changed = 1; + continue; + } + + index = find_section( sections, count, a->name ); + if( index == SIZE_MAX ) + { + snprintf( err, errsz, "section %s does not exist", a->name ); + return( -1 ); + } + + if( a->type == MCPU_OBJECT_EDIT_REMOVE ) + { + if( safe_remove_section( file, sections, count, index, + "--remove-section", err, errsz ) ) + return( -1 ); + *changed = 1; + } + else if( a->type == MCPU_OBJECT_EDIT_RENAME ) + { + char *p; + + if( !section_user_editable( file, §ions[index] ) ) + { + snprintf( err, errsz, "--rename-section cannot rename section %s", + a->name ); + return( -1 ); + } + if( !a->new_name || !*a->new_name ) + { + snprintf( err, errsz, "new section name must not be empty" ); + return( -1 ); + } + if( reserved_section_name( a->new_name ) ) + { + snprintf( err, errsz, "cannot rename metadata to reserved name %s", + a->new_name ); + return( -1 ); + } + if( find_section( sections, count, a->new_name ) != SIZE_MAX ) + { + snprintf( err, errsz, "section %s already exists", a->new_name ); + return( -1 ); + } + p = strdup( a->new_name ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + free( sections[index].name ); + sections[index].name = p; + *changed = 1; + } + else if( a->type == MCPU_OBJECT_EDIT_UPDATE ) + { + if( !section_user_editable( file, §ions[index] ) ) + { + snprintf( err, errsz, "--update-section cannot update section %s", + a->name ); + return( -1 ); + } + if( replace_section_data( §ions[index], a->data, a->size, + err, errsz ) ) + return( -1 ); + *changed = 1; + } + else + { + snprintf( err, errsz, "internal error: invalid edit action" ); + return( -1 ); + } + } + + *psections = sections; + *pcount = count; + return( 0 ); + +} /* End of apply_options() */ + + +static int +build_output( const mcpu_edit_file *file, + mcpu_edit_section *sections, size_t count, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + __mpu_uint16_t *map = NULL; + mcpu_edit_shdr *nsh = NULL; + size_t *source_index = NULL; + unsigned char *result = NULL; + unsigned char *newshstr = NULL; + size_t newshstr_size = 1; + size_t newshstr_cap = 64; + __mpu_uint16_t newnum = 0; + __mpu_uint16_t newshstrndx = MELF_SHN_UNDEF; + __mpu_uint64_t fixed_end = MELF64_EHDR_SIZE; + __mpu_uint64_t cur; + __mpu_uint64_t shoff; + __mpu_uint64_t total; + int overflow = 0; + size_t i, j; + int rc = -1; + + map = (__mpu_uint16_t *)malloc( (size_t)file->shnum * sizeof( *map ) ); + if( !map ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + for( i = 0; i < file->shnum; ++i ) + map[i] = MELF_SHN_UNDEF; + + for( i = 0; i < count; ++i ) + if( !sections[i].removed ) + { + if( newnum == UINT16_MAX ) + { + snprintf( err, errsz, "too many retained sections" ); + goto out; + } + if( !sections[i].is_new ) + map[sections[i].old_index] = newnum; + if( !sections[i].is_new && sections[i].old_index == file->shstrndx ) + newshstrndx = newnum; + ++newnum; + } + + if( newshstrndx == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "internal error: .shstrtab was removed" ); + goto out; + } + + nsh = (mcpu_edit_shdr *)calloc( newnum, sizeof( *nsh ) ); + source_index = (size_t *)calloc( newnum, sizeof( *source_index ) ); + newshstr = (unsigned char *)malloc( newshstr_cap ); + if( !nsh || !source_index || !newshstr ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + newshstr[0] = 0; + + for( i = 0, j = 0; i < count; ++i ) + if( !sections[i].removed ) + { + size_t n = strlen( sections[i].name ) + 1; + mcpu_edit_shdr sh = sections[i].shdr; + + if( i == 0 ) + sh.name = 0; + else + { + if( n > SIZE_MAX - newshstr_size || + newshstr_size + n > UINT32_MAX ) + { + snprintf( err, errsz, "section-name table overflow" ); + goto out; + } + if( newshstr_size + n > newshstr_cap ) + { + size_t cap = newshstr_cap; + unsigned char *q; + + while( cap < newshstr_size + n ) + { + if( cap > SIZE_MAX / 2 ) + { + snprintf( err, errsz, "section-name table overflow" ); + goto out; + } + cap *= 2; + } + q = (unsigned char *)realloc( newshstr, cap ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + newshstr = q; + newshstr_cap = cap; + } + sh.name = (__mpu_uint32_t)newshstr_size; + memcpy( newshstr + newshstr_size, sections[i].name, n ); + newshstr_size += n; + } + + if( !sections[i].is_new ) + { + if( sh.link ) + { + if( sh.link >= file->shnum || map[sh.link] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained section %s depends on removed section", + sections[i].name ); + goto out; + } + sh.link = map[sh.link]; + } + if( sh.type == MELF_SHT_RELA && sh.info ) + { + if( sh.info >= file->shnum || map[sh.info] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained relocation section targets removed section" ); + goto out; + } + sh.info = map[sh.info]; + } + } + if( !sections[i].is_new && sections[i].old_index == file->shstrndx ) + sh.size = newshstr_size; + else + sh.size = sections[i].data_size; + nsh[j] = sh; + source_index[j] = i; + ++j; + } + + /* .shstrtab size is final only after all retained names were appended. */ + nsh[newshstrndx].size = newshstr_size; + + if( file->phnum ) + { + __mpu_uint64_t end = file->phoff + + (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE; + + if( end > fixed_end ) + fixed_end = end; + for( i = 0; i < file->phnum; ++i ) + { + const unsigned char *p = file->data + (size_t)file->phoff + + i * MELF64_PHDR_SIZE; + __mpu_uint32_t type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, + file->encoding ); + __mpu_uint64_t off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, + file->encoding ); + __mpu_uint64_t filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, + file->encoding ); + + if( type == MELF_PT_LOAD && filesz ) + { + if( !cpu_melf_span( file->size, off, filesz ) ) + { + snprintf( err, errsz, "PT_LOAD has an invalid file range" ); + goto out; + } + if( off + filesz > fixed_end ) + fixed_end = off + filesz; + } + } + } + + for( i = 0; i < count; ++i ) + if( !sections[i].removed && !sections[i].is_new && + ( sections[i].shdr.flags & MELF_SHF_ALLOC ) && + sections[i].shdr.type != MELF_SHT_NOBITS && + sections[i].shdr.size ) + { + __mpu_uint64_t end = sections[i].shdr.offset + sections[i].shdr.size; + + if( end > fixed_end ) + fixed_end = end; + } + + if( fixed_end > file->size ) + { + snprintf( err, errsz, "fixed MELF image exceeds file size" ); + goto out; + } + + cur = fixed_end; + for( i = 0; i < newnum; ++i ) + { + size_t si = source_index[i]; + + if( nsh[i].flags & MELF_SHF_ALLOC ) + continue; + if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size ) + continue; + cur = cpu_melf_align_up( cur, nsh[i].align ? nsh[i].align : 1, + &overflow ); + if( overflow ) + { + snprintf( err, errsz, "MELF layout overflow" ); + goto out; + } + nsh[i].offset = cur; + if( nsh[i].size > UINT64_MAX - cur ) + { + snprintf( err, errsz, "MELF layout overflow" ); + goto out; + } + cur += nsh[i].size; + (void)si; + } + + shoff = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow || (__mpu_uint64_t)newnum > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE ) + { + snprintf( err, errsz, "section table overflow" ); + goto out; + } + total = shoff + (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, errsz, "MELF file is too large for this host" ); + goto out; + } + + result = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 ); + if( !result ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + if( fixed_end ) + memcpy( result, file->data, (size_t)fixed_end ); + + for( i = 0; i < newnum; ++i ) + { + size_t si = source_index[i]; + const mcpu_edit_section *s = §ions[si]; + + if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size ) + continue; + if( !cpu_melf_span( (size_t)total, nsh[i].offset, nsh[i].size ) ) + { + snprintf( err, errsz, "section copy exceeds output bounds" ); + goto out; + } + if( !s->is_new && s->old_index == file->shstrndx ) + memcpy( result + (size_t)nsh[i].offset, newshstr, newshstr_size ); + else if( s->data_size ) + memcpy( result + (size_t)nsh[i].offset, s->data, s->data_size ); + } + + /* Remap section indices stored inside retained symbol tables. */ + for( i = 0; i < newnum; ++i ) + if( nsh[i].type == MELF_SHT_SYMTAB || nsh[i].type == MELF_SHT_DYNSYM ) + { + size_t n; + + if( nsh[i].entsize != MELF64_SYM_SIZE || + nsh[i].size % MELF64_SYM_SIZE ) + { + snprintf( err, errsz, "retained symbol table is malformed" ); + goto out; + } + n = (size_t)( nsh[i].size / MELF64_SYM_SIZE ); + for( j = 0; j < n; ++j ) + { + unsigned char *p = result + (size_t)nsh[i].offset + + j * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = cpu_melf_get16( + p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + + if( shndx == MELF_SHN_UNDEF || shndx >= MELF_SHN_LORESERVE ) + continue; + if( shndx >= file->shnum || map[shndx] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained symbol refers to removed section" ); + goto out; + } + cpu_melf_put16( p + MELF64_SYM_ST_SHNDX_OFF, map[shndx], + file->encoding ); + } + } + + cpu_melf_put64( result + MELF64_EHDR_E_SHOFF_OFF, shoff, file->encoding ); + cpu_melf_put16( result + MELF64_EHDR_E_SHNUM_OFF, newnum, file->encoding ); + cpu_melf_put16( result + MELF64_EHDR_E_SHSTRNDX_OFF, + newshstrndx, file->encoding ); + + for( i = 0; i < newnum; ++i ) + put_shdr( result + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &nsh[i] ); + + *out = result; + *out_size = (size_t)total; + result = NULL; + rc = 0; + +out: + free( result ); + free( newshstr ); + free( source_index ); + free( nsh ); + free( map ); + return( rc ); + +} /* End of build_output() */ + + +int +mcpu_object_edit_data( const unsigned char *data, size_t size, + const mcpu_object_edit_options *options, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_file file; + mcpu_edit_section *sections = NULL; + size_t count = 0; + int local_changed = 0; + int rc = -1; + + if( !data || !options || !out || !out_size || !changed ) + return( -1 ); + *out = NULL; + *out_size = 0; + *changed = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) || + sections_from_file( &file, §ions, &count, err, errsz ) ) + goto out; + + if( apply_options( &file, §ions, &count, options, &local_changed, + err, errsz ) ) + goto out; + + if( !local_changed ) + { + rc = copy_original( data, size, out, out_size, err, errsz ); + goto out; + } + if( build_output( &file, sections, count, out, out_size, err, errsz ) ) + goto out; + + *changed = 1; + rc = 0; + +out: + sections_free( sections, count ); + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_edit_data() */ + + +int +mcpu_object_strip_data( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_file file; + mcpu_edit_section *sections = NULL; + size_t count = 0; + size_t i; + int any = 0; + int rc = -1; + + if( !data || !out || !out_size || !changed ) + return( -1 ); + *out = NULL; + *out_size = 0; + *changed = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) || + sections_from_file( &file, §ions, &count, err, errsz ) ) + goto out; + + if( file.type != MELF_ET_REL ) + { + for( i = 1; i < count; ++i ) + if( sections[i].shdr.type == MELF_SHT_SYMTAB && + !( sections[i].shdr.flags & MELF_SHF_ALLOC ) && + !section_link_references( sections, count, i ) ) + { + __mpu_uint32_t link = sections[i].shdr.link; + + sections[i].removed = 1; + any = 1; + if( link && link < file.shnum && link != file.shstrndx && + sections[link].shdr.type == MELF_SHT_STRTAB ) + { + size_t k; + int referenced = 0; + + for( k = 1; k < count; ++k ) + if( !sections[k].removed && !sections[k].is_new && + sections[k].shdr.link == link ) + referenced = 1; + if( !referenced ) + sections[link].removed = 1; + } + } + } + + if( !any ) + { + rc = copy_original( data, size, out, out_size, err, errsz ); + goto out; + } + if( build_output( &file, sections, count, out, out_size, err, errsz ) ) + goto out; + + *changed = 1; + rc = 0; + +out: + sections_free( sections, count ); + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_strip_data() */ + + +int +mcpu_object_dump_section( const unsigned char *data, size_t size, + const char *name, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + mcpu_edit_file file; + size_t i; + int rc = -1; + + if( !data || !name || !out || !out_size ) + return( -1 ); + *out = NULL; + *out_size = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) ) + return( -1 ); + + for( i = 1; i < file.shnum; ++i ) + { + const char *s = name_at( &file, file.shdr[i].name ); + + if( s && !strcmp( s, name ) ) + { + if( file.shdr[i].type == MELF_SHT_NOBITS ) + { + snprintf( err, errsz, "section %s has no file contents", name ); + goto out; + } + rc = copy_original( file.data + (size_t)file.shdr[i].offset, + (size_t)file.shdr[i].size, + out, out_size, err, errsz ); + goto out; + } + } + snprintf( err, errsz, "section %s does not exist", name ); + +out: + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_dump_section() */ diff --git a/utils/mcpu-object-edit.h b/utils/mcpu-object-edit.h new file mode 100644 index 0000000..2e04d98 --- /dev/null +++ b/utils/mcpu-object-edit.h @@ -0,0 +1,49 @@ +#ifndef _MCPU_OBJECT_EDIT_H_ +#define _MCPU_OBJECT_EDIT_H_ 1 + +#include <stddef.h> + + +typedef enum mcpu_object_edit_action_type +{ + MCPU_OBJECT_EDIT_REMOVE = 1, + MCPU_OBJECT_EDIT_RENAME, + MCPU_OBJECT_EDIT_UPDATE, + MCPU_OBJECT_EDIT_ADD +} mcpu_object_edit_action_type; + + +typedef struct mcpu_object_edit_action +{ + mcpu_object_edit_action_type type; + const char *name; + const char *new_name; + const unsigned char *data; + size_t size; +} mcpu_object_edit_action; + + +typedef struct mcpu_object_edit_options +{ + const char *const *only_sections; + size_t only_count; + const mcpu_object_edit_action *actions; + size_t action_count; +} mcpu_object_edit_options; + + +int mcpu_object_strip_data( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ); + +int mcpu_object_edit_data( const unsigned char *data, size_t size, + const mcpu_object_edit_options *options, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ); + +int mcpu_object_dump_section( const unsigned char *data, size_t size, + const char *name, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-object.c b/utils/mcpu-object.c new file mode 100644 index 0000000..0b36788 --- /dev/null +++ b/utils/mcpu-object.c @@ -0,0 +1,1489 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-object.h" +#include "mcpu-melf.h" + +typedef struct blob +{ + unsigned char *p; + size_t n; + size_t cap; +} blob; + +typedef struct wsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t align; + __mpu_uint64_t entsize; + __mpu_uint32_t link; + __mpu_uint32_t info; + unsigned char *owned; + const unsigned char *payload; + size_t size; + __mpu_uint64_t off; + __mpu_uint32_t name_off; + unsigned logical; +} wsec; + +typedef struct rsh +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} rsh; + +static const unsigned object_sections[] = +{ + CPU_OBJ_SEC_INIT, + CPU_OBJ_SEC_TEXT, + CPU_OBJ_SEC_FINI, + CPU_OBJ_SEC_DATA, + CPU_OBJ_SEC_BSS, + CPU_OBJ_SEC_CONTROL, + CPU_OBJ_SEC_INPUT, + CPU_OBJ_SEC_OUTPUT, + CPU_OBJ_SEC_TRACE +}; + +#define OBJECT_SECTION_COUNT \ + (sizeof(object_sections) / sizeof(object_sections[0])) + +static int +object_add_symbol( cpu_asm_object *o, const char *name, unsigned section, + unsigned binding, unsigned type, __mpu_uint64_t value, + char *err, size_t es) +{ + cpu_asm_symbol *q; + + if (!o || !name) + return( -1 ); + + q = (cpu_asm_symbol *)realloc( o->symbols, + (o->symbol_count + 1) * sizeof(*q)); + if (!q) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->symbols = q; + memset( &o->symbols[o->symbol_count], 0, + sizeof(o->symbols[o->symbol_count])); + o->symbols[o->symbol_count].name = strdup( name ); + if (!o->symbols[o->symbol_count].name) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->symbols[o->symbol_count].section = section; + o->symbols[o->symbol_count].binding = binding; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + ++o->symbol_count; + return( 0 ); +} + +static int +object_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, + size_t symbol_index, __mpu_uint64_t offset, + __mpu_int64_t addend, char *err, size_t es) +{ + cpu_asm_reloc *q; + + q = (cpu_asm_reloc *)realloc( o->relocs, + (o->reloc_count + 1) * sizeof(*q)); + if (!q) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->relocs = q; + o->relocs[o->reloc_count].section = section; + o->relocs[o->reloc_count].type = type; + o->relocs[o->reloc_count].symbol_index = symbol_index; + o->relocs[o->reloc_count].offset = offset; + o->relocs[o->reloc_count].addend = addend; + ++o->reloc_count; + return( 0 ); +} + +static int +blob_grow( blob *b, size_t add ) +{ + size_t need; + size_t cap; + unsigned char *q; + + if (add > SIZE_MAX - b->n) + return( -1 ); + need = b->n + add; + if (need <= b->cap) + return( 0 ); + + cap = b->cap ? b->cap : 64; + while (cap < need) + { + if (cap > SIZE_MAX / 2) + return( -1 ); + cap *= 2; + } + + q = (unsigned char *)realloc( b->p, cap ); + if (!q) + return( -1 ); + b->p = q; + b->cap = cap; + return( 0 ); +} + +static int +blob_put( blob *b, const void *p, size_t n ) +{ + if( blob_grow( b, n ) ) + return( -1 ); + if (n) + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +blob_string( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + + if (b->n > UINT32_MAX || n > (size_t)UINT32_MAX - b->n) + return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( blob_put( b, s, n ) ); +} + +static void +free_wsecs( wsec *s, size_t n ) +{ + size_t i; + for( i = 0; i < n; ++i ) + free( s[i].owned ); +} + +static void +put_ehdr64( unsigned char *p, int data, __mpu_uint16_t type, + __mpu_uint64_t shoff, __mpu_uint16_t shnum, + __mpu_uint16_t shstr) +{ + memset( p, 0, MELF64_EHDR_SIZE ); + p[MELF_EI_MAG0] = MELFMAG0; + p[MELF_EI_MAG1] = MELFMAG1; + p[MELF_EI_MAG2] = MELFMAG2; + p[MELF_EI_MAG3] = MELFMAG3; + p[MELF_EI_CLASS] = MELF_CLASS_64; + p[MELF_EI_DATA] = (unsigned char)data; + p[MELF_EI_VERSION] = MELF_EV_CURRENT; + p[MELF_EI_OSABI] = MELF_OSABI_MCPU; + p[MELF_EI_ABIVERSION] = MELF_ABIVERSION_CURRENT; + + cpu_melf_put16( p + MELF64_EHDR_E_TYPE_OFF, type, data ); + cpu_melf_put16( p + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( p + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + cpu_melf_put64( p + MELF64_EHDR_E_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_INIT_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_LIFE_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_FINI_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_PHOFF_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( p + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHENTSIZE_OFF, 0, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHNUM_OFF, 0, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHNUM_OFF, shnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHSTRNDX_OFF, shstr, data ); +} + +static void +put_shdr64( unsigned char *p, const wsec *s, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, s->name_off, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, s->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, s->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, 0, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, s->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, s->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, s->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, s->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, s->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, s->entsize, data ); +} + +static void +get_shdr64( const unsigned char *p, int data, rsh *s ) +{ + memset( s, 0, sizeof( *s ) ); + s->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + s->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + s->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + s->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + s->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + s->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + s->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + s->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + s->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + s->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +shdr_is_zero( const rsh *s ) +{ + return s->name == 0 && s->type == 0 && s->flags == 0 && s->addr == 0 && + s->off == 0 && s->size == 0 && s->link == 0 && s->info == 0 && + s->align == 0 && s->entsize == 0; +} + +static __mpu_uint64_t +sec_flags( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SHFLAGS_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SHFLAGS_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SHFLAGS_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SHFLAGS_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SHFLAGS_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SHFLAGS_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static const char * +sec_name( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SEC_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_TRACE ); + default: return( "" ); + } +} + +static const char * +rela_name( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_RELA_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_RELA_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_RELA_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_RELA_DATA ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_RELA_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_RELA_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_RELA_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_RELA_TRACE ); + default: return( NULL ); + } +} + +static int +obj_sec( const cpu_asm_object *o, unsigned logical, const unsigned char **p, + size_t *n, __mpu_uint64_t *a, int *present) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: + *p = o->init; + *n = o->init_size; + *a = o->init_align; + *present = o->has_init; + return( 0 ); + case CPU_OBJ_SEC_TEXT: + *p = o->text; + *n = o->text_size; + *a = o->text_align; + *present = o->has_text; + return( 0 ); + case CPU_OBJ_SEC_FINI: + *p = o->fini; + *n = o->fini_size; + *a = o->fini_align; + *present = o->has_fini; + return( 0 ); + case CPU_OBJ_SEC_DATA: + *p = o->data; + *n = o->data_size; + *a = o->data_align; + *present = o->has_data || o->data_size != 0; + return( 0 ); + case CPU_OBJ_SEC_BSS: + *p = NULL; + *n = o->bss_size; + *a = o->bss_align; + *present = o->has_bss || o->bss_size != 0; + return( 0 ); + case CPU_OBJ_SEC_CONTROL: + *p = o->control; + *n = o->control_size; + *a = o->control_align; + *present = o->has_control || o->control_size != 0; + return( 0 ); + case CPU_OBJ_SEC_INPUT: + *p = o->input; + *n = o->input_size; + *a = o->input_align; + *present = o->has_input || o->input_size != 0; + return( 0 ); + case CPU_OBJ_SEC_OUTPUT: + *p = o->output; + *n = o->output_size; + *a = o->output_align; + *present = o->has_output || o->output_size != 0; + return( 0 ); + case CPU_OBJ_SEC_TRACE: + *p = o->trace; + *n = o->trace_size; + *a = o->trace_align; + *present = o->has_trace || o->trace_size != 0; + return( 0 ); + default: + return( -1 ); + } +} + +static int +logical_index( const __mpu_uint16_t idx[OBJECT_SECTION_COUNT], unsigned logical ) +{ + size_t i; + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + if (object_sections[i] == logical) + return( idx[i] ); + return( 0 ); +} + +static int +validate_object_for_write( const cpu_asm_object *o, char *err, size_t es ) +{ + size_t i; + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if( obj_sec( o, object_sections[i], &p, &n, &a, &present ) ) + return( -1 ); + if (!present) + continue; + if( a == 0 || !cpu_melf_is_pow2( a ) ) + { + snprintf( err, es, "invalid alignment for %s", sec_name( object_sections[i] ) ); + return( -1 ); + } + if (object_sections[i] != CPU_OBJ_SEC_BSS && n && !p) + { + snprintf( err, es, "missing payload for %s", sec_name( object_sections[i] ) ); + return( -1 ); + } + } + + for( i = 0; i < o->symbol_count; ++i ) + { + const cpu_asm_symbol *s = &o->symbols[i]; + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if (!s->name || !*s->name) + { + snprintf( err, es, "empty symbol name" ); + return( -1 ); + } + if (s->binding > CPU_OBJ_BIND_WEAK || s->type > CPU_OBJ_TYPE_FUNC) + { + snprintf( err, es, "invalid symbol attributes: %s", s->name ); + return( -1 ); + } + if (s->section == CPU_OBJ_SEC_UNDEF) + { + if (s->binding == CPU_OBJ_BIND_LOCAL || s->value != 0 || s->size != 0) + { + snprintf( err, es, "malformed undefined symbol: %s", s->name ); + return( -1 ); + } continue; + } + if( obj_sec( o, s->section, &p, &n, &a, &present ) || !present ) + { + snprintf( err, es, "symbol %s belongs to absent section", s->name ); + return( -1 ); + } + if (s->value > n || s->size > (__mpu_uint64_t)n - s->value) + { + snprintf( err, es, "symbol range outside section: %s", s->name ); + return( -1 ); + } + } + + for( i = 0; i < o->reloc_count; ++i ) + { + const cpu_asm_reloc *r = &o->relocs[i]; + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if (r->type < CPU_RELOC_CODE64 || r->type > CPU_RELOC_CPTR64_DATA) + { + snprintf( err, es, "invalid relocation type" ); + return( -1 ); + } + if (r->symbol_index >= o->symbol_count) + { + snprintf( err, es, "bad relocation symbol index" ); + return( -1 ); + } + if (r->section == CPU_OBJ_SEC_BSS) + { + snprintf( err, es, "relocations cannot patch SHT_NOBITS .bss" ); + return( -1 ); + } + if( obj_sec( o, r->section, &p, &n, &a, &present ) || !present ) + { + snprintf( err, es, "relocation refers to absent source section" ); + return( -1 ); + } + if (r->offset > n || CPU_PTR64_SIZE > n - (size_t)r->offset) + { + snprintf( err, es, "relocation field outside source section" ); + return( -1 ); + } + } + + return( 0 ); +} + +int +cpu_object_write( const char *path, const cpu_asm_object *o, + char *err, size_t es) +{ + wsec s[32]; + size_t ns = 1; + size_t i, j; + __mpu_uint16_t secidx[OBJECT_SECTION_COUNT] = {0}; + __mpu_uint16_t relaidx[OBJECT_SECTION_COUNT] = {0}; + int data = cpu_melf_data_encoding(); + int ov = 0; + blob str = {0}; + blob shstr = {0}; + __mpu_uint32_t *nameoff = NULL; + __mpu_uint32_t *symmap = NULL; + size_t nlocal = 0, nonlocal = 0, symcount; + unsigned char *sympay = NULL; + unsigned char *file = NULL; + size_t symsz; + __mpu_uint16_t symsec, strsec, targetsec, shstrsec; + MElf_McpuTarget mt; + unsigned char mtbuf[MELF_MCPU_TARGET_SIZE]; + __mpu_uint64_t cur, shoff, total; + + if (!path || !o) + return( -1 ); + if( validate_object_for_write( o, err, es ) ) + return( -1 ); + + memset( s, 0, sizeof( s ) ); + if( blob_put( &str, "", 1 ) || blob_put( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + unsigned logical = object_sections[i]; + + if( obj_sec( o, logical, &p, &n, &a, &present ) ) + goto fail; + if (!present) + continue; + if (ns >= sizeof(s) / sizeof(s[0])) + { + snprintf( err, es, "too many MELF sections" ); + goto fail; + } + + secidx[i] = (__mpu_uint16_t)ns; + s[ns].name = sec_name( logical ); + s[ns].type = logical == CPU_OBJ_SEC_BSS ? MELF_SHT_NOBITS + : MELF_SHT_PROGBITS; + s[ns].flags = sec_flags( logical ); + s[ns].align = a; + s[ns].payload = p; + s[ns].size = n; + s[ns].logical = logical; + ++ns; + } + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + size_t cnt = 0; + const char *rn; + + for( j = 0; j < o->reloc_count; ++j ) + if (o->relocs[j].section == object_sections[i]) + ++cnt; + if (!cnt) + continue; + + rn = rela_name( object_sections[i] ); + if (!rn || !secidx[i]) + { + snprintf( err, es, "invalid relocation source section" ); + goto fail; + } + if (ns >= sizeof(s) / sizeof(s[0])) + { + snprintf( err, es, "too many MELF sections" ); + goto fail; + } + + relaidx[i] = (__mpu_uint16_t)ns; + s[ns].name = rn; + s[ns].type = MELF_SHT_RELA; + s[ns].align = 8; + s[ns].entsize = MELF64_RELA_SIZE; + s[ns].info = secidx[i]; + s[ns].size = cnt * MELF64_RELA_SIZE; + s[ns].logical = object_sections[i]; + ++ns; + } + + symsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_SYMTAB; + s[ns].type = MELF_SHT_SYMTAB; + s[ns].align = 8; + s[ns].entsize = MELF64_SYM_SIZE; + ++ns; + + strsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_STRTAB; + s[ns].type = MELF_SHT_STRTAB; + s[ns].align = 1; + ++ns; + + targetsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_MCPU_TARGET; + s[ns].type = MELF_SHT_MCPU_TARGET; + s[ns].align = 8; + s[ns].entsize = MELF_MCPU_TARGET_SIZE; + s[ns].size = MELF_MCPU_TARGET_SIZE; + ++ns; + + shstrsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_SHSTRTAB; + s[ns].type = MELF_SHT_STRTAB; + s[ns].align = 1; + ++ns; + + nameoff = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, + sizeof(*nameoff)); + symmap = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, + sizeof(*symmap)); + if (!nameoff || !symmap) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + for( i = 0; i < o->symbol_count; ++i ) + { + if( blob_string( &str, o->symbols[i].name, &nameoff[i] ) ) + { + snprintf( err, es, "symbol string table too large" ); + goto fail; + } + if (o->symbols[i].binding == CPU_OBJ_BIND_LOCAL) + ++nlocal; + else + ++nonlocal; + } + + if (nlocal > UINT32_MAX - 1 || nonlocal > UINT32_MAX - 1 - nlocal) + { + snprintf( err, es, "too many symbols" ); + goto fail; + } + symcount = 1 + nlocal + nonlocal; + if (symcount > SIZE_MAX / MELF64_SYM_SIZE) + { + snprintf( err, es, "symbol table too large" ); + goto fail; + } + symsz = symcount * MELF64_SYM_SIZE; + sympay = (unsigned char *)calloc( symsz ? symsz : 1, 1 ); + if (!sympay) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + { + size_t li = 1; + size_t gi = 1 + nlocal; + + for( i = 0; i < o->symbol_count; ++i ) + { + const cpu_asm_symbol *x = &o->symbols[i]; + size_t mi = x->binding == CPU_OBJ_BIND_LOCAL ? li++ : gi++; + unsigned bind; + unsigned type; + __mpu_uint16_t shn = MELF_SHN_UNDEF; + unsigned char *q = sympay + mi * MELF64_SYM_SIZE; + + if (x->binding == CPU_OBJ_BIND_LOCAL) + bind = MELF_STB_LOCAL; + else if (x->binding == CPU_OBJ_BIND_WEAK) + bind = MELF_STB_WEAK; + else + bind = MELF_STB_GLOBAL; + + if (x->type == CPU_OBJ_TYPE_FUNC) + type = MELF_STT_FUNC; + else if (x->type == CPU_OBJ_TYPE_OBJECT) + type = MELF_STT_OBJECT; + else + type = MELF_STT_NOTYPE; + + if (x->section != CPU_OBJ_SEC_UNDEF) + { + int idx = logical_index( secidx, x->section ); + if (!idx) + { + snprintf( err, es, "symbol %s belongs to absent section", x->name ); + goto fail; + } + shn = (__mpu_uint16_t)idx; + } + + symmap[i] = (__mpu_uint32_t)mi; + cpu_melf_put32( q + MELF64_SYM_ST_NAME_OFF, nameoff[i], data ); + q[MELF64_SYM_ST_INFO_OFF] = MELF64_ST_INFO( bind, type ); + q[MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( q + MELF64_SYM_ST_SHNDX_OFF, shn, data ); + cpu_melf_put64( q + MELF64_SYM_ST_VALUE_OFF, x->value, data ); + cpu_melf_put64( q + MELF64_SYM_ST_SIZE_OFF, x->size, data ); + } + } + + s[symsec].owned = sympay; + s[symsec].payload = sympay; + s[symsec].size = symsz; + s[symsec].link = strsec; + s[symsec].info = (__mpu_uint32_t)(1 + nlocal); + sympay = NULL; + + s[strsec].owned = str.p; + s[strsec].payload = str.p; + s[strsec].size = str.n; + str.p = NULL; + + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( mtbuf, &mt, data ); + s[targetsec].owned = (unsigned char *)malloc( MELF_MCPU_TARGET_SIZE ); + if (!s[targetsec].owned) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + memcpy( s[targetsec].owned, mtbuf, MELF_MCPU_TARGET_SIZE ); + s[targetsec].payload = s[targetsec].owned; + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + if (relaidx[i]) + { + size_t cnt = 0; + wsec *r = &s[relaidx[i]]; + + r->link = symsec; + r->owned = (unsigned char *)calloc( r->size ? r->size : 1, 1 ); + if (!r->owned) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + r->payload = r->owned; + + for( j = 0; j < o->reloc_count; ++j ) + { + const cpu_asm_reloc *x; + unsigned char *q; + + if (o->relocs[j].section != object_sections[i]) + continue; + x = &o->relocs[j]; + q = r->owned + cnt * MELF64_RELA_SIZE; + cpu_melf_put64( q + MELF64_RELA_R_OFFSET_OFF, x->offset, data ); + cpu_melf_put64( q + MELF64_RELA_R_INFO_OFF, + MELF64_R_INFO( symmap[x->symbol_index], x->type ), data); + cpu_melf_put64( q + MELF64_RELA_R_ADDEND_OFF, + (__mpu_uint64_t)x->addend, data); + ++cnt; + } + } + } + + for( i = 1; i < ns; ++i ) + if( blob_string( &shstr, s[i].name, &s[i].name_off ) ) + { + snprintf( err, es, "section-name string table too large" ); + goto fail; + } + + s[shstrsec].owned = shstr.p; + s[shstrsec].payload = shstr.p; + s[shstrsec].size = shstr.n; + shstr.p = NULL; + + cur = MELF64_EHDR_SIZE; + for( i = 1; i < ns; ++i ) + { + cur = cpu_melf_align_up( cur, s[i].align ? s[i].align : 1, &ov ); + if (ov || cur > SIZE_MAX) + { + snprintf( err, es, "MELF file layout overflow" ); + goto fail; + } + s[i].off = cur; + if (s[i].type != MELF_SHT_NOBITS) + { + if ((__mpu_uint64_t)s[i].size > UINT64_MAX - cur) + { + snprintf( err, es, "MELF file layout overflow" ); + goto fail; + } + cur += s[i].size; + } + } + + shoff = cpu_melf_align_up( cur, 8, &ov ); + if (ov || ns > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - ns * MELF64_SHDR_SIZE) + { + snprintf( err, es, "MELF section table overflow" ); + goto fail; + } + total = shoff + ns * MELF64_SHDR_SIZE; + if (total > SIZE_MAX) + { + snprintf( err, es, "MELF file too large for this host" ); + goto fail; + } + + file = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 ); + if (!file) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + put_ehdr64( file, data, MELF_ET_REL, shoff, (__mpu_uint16_t)ns, shstrsec ); + for( i = 1; i < ns; ++i ) + if (s[i].type != MELF_SHT_NOBITS && s[i].size) + memcpy( file + (size_t)s[i].off, s[i].payload, s[i].size ); + for( i = 0; i < ns; ++i ) + put_shdr64( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &s[i], data ); + + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) + goto fail; + + free( file ); + free( nameoff ); + free( symmap ); + free_wsecs( s, ns ); + return( 0 ); + +fail: + free( file ); + free( nameoff ); + free( symmap ); + free( sympay ); + free( str.p ); + free( shstr.p ); + free_wsecs( s, ns ); + return( -1 ); +} + +static int +validate_ident( const unsigned char *p, size_t n, __mpu_uint16_t want, + int *data, char *err, size_t es) +{ + size_t i; + + if (n < MELF64_EHDR_SIZE || p[MELF_EI_MAG0] != MELFMAG0 || + p[MELF_EI_MAG1] != MELFMAG1 || p[MELF_EI_MAG2] != MELFMAG2 || + p[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "not a MELF file" ); + return( -1 ); + } + if (p[MELF_EI_CLASS] != MELF_CLASS_64) + { + snprintf( err, es, "unsupported MELF class" ); + return( -1 ); + } + + *data = p[MELF_EI_DATA]; + if (*data != MELF_DATA_LSB && *data != MELF_DATA_MSB) + { + snprintf( err, es, "unsupported MELF data encoding" ); + return( -1 ); + } + if (p[MELF_EI_VERSION] != MELF_EV_CURRENT || + p[MELF_EI_OSABI] != MELF_OSABI_MCPU || + p[MELF_EI_ABIVERSION] != MELF_ABIVERSION_CURRENT || + cpu_melf_get16( p + MELF64_EHDR_E_TYPE_OFF, *data ) != want || + cpu_melf_get16( p + MELF64_EHDR_E_MACHINE_OFF, *data ) != MELF_EM_MCPU || + cpu_melf_get32( p + MELF64_EHDR_E_VERSION_OFF, *data ) != MELF_EV_CURRENT || + cpu_melf_get32( p + MELF64_EHDR_E_FLAGS_OFF, *data ) != MELF_EF_MCPU_NONE || + cpu_melf_get16( p + MELF64_EHDR_E_EHSIZE_OFF, *data ) != MELF64_EHDR_SIZE) + { + snprintf( err, es, "unsupported MELF header" ); + return( -1 ); + } + + for( i = MELF_EI_PAD; i < MELF_EI_NIDENT; ++i ) + if (p[i] != 0) + { + snprintf( err, es, "nonzero reserved e_ident byte" ); + return( -1 ); + } + return( 0 ); +} + +static int +name_at( const unsigned char *tab, size_t n, __mpu_uint32_t off, + const char **s) +{ + if (off >= n) + return( -1 ); + *s = (const char *)tab + off; + return( memchr( *s, 0, n - off ) ? 0 : -1 ); +} + +static int +map_name_logical( const char *n, unsigned *logical ) +{ + size_t i; + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + if( !strcmp( n, sec_name( object_sections[i] ) ) ) + { + *logical = object_sections[i]; + return( 0 ); + } + return( -1 ); +} + +static int +load_payload( unsigned char **dst, size_t *dn, const unsigned char *f, + size_t fn, const rsh *s, char *err, size_t es) +{ + if (s->type == MELF_SHT_NOBITS) + { + snprintf( err, es, "attempt to load SHT_NOBITS payload" ); + return( -1 ); + } + if( s->size > SIZE_MAX || !cpu_melf_span( fn, s->off, s->size ) ) + { + snprintf( err, es, "bad section file range" ); + return( -1 ); + } + if (s->size) + { + *dst = (unsigned char *)malloc( (size_t)s->size ); + if (!*dst) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memcpy( *dst, f + (size_t)s->off, (size_t)s->size ); + } + *dn = (size_t)s->size; + return( 0 ); +} + +static int +u64_to_s64( __mpu_uint64_t u, __mpu_int64_t *out ) +{ + if (u <= (__mpu_uint64_t)INT64_MAX) + { + *out = (__mpu_int64_t)u; + return( 0 ); + } + *out = -1 - (__mpu_int64_t)(UINT64_MAX - u); + return( 0 ); +} + +static int +load_allocated_section( cpu_asm_object *o, unsigned l, const rsh *s, + const unsigned char *f, size_t fn, + char *err, size_t es) +{ + switch (l) + { + case CPU_OBJ_SEC_INIT: + if (o->has_init) goto duplicate; + if( load_payload( (unsigned char **)&o->init, &o->init_size, + f, fn, s, err, es)) return( -1 ); + o->init_align = s->align; + o->has_init = 1; + return( 0 ); + case CPU_OBJ_SEC_TEXT: + if (o->has_text) goto duplicate; + if( load_payload( (unsigned char **)&o->text, &o->text_size, + f, fn, s, err, es)) return( -1 ); + o->text_align = s->align; + o->has_text = 1; + return( 0 ); + case CPU_OBJ_SEC_FINI: + if (o->has_fini) goto duplicate; + if( load_payload( (unsigned char **)&o->fini, &o->fini_size, + f, fn, s, err, es)) return( -1 ); + o->fini_align = s->align; + o->has_fini = 1; + return( 0 ); + case CPU_OBJ_SEC_DATA: + if (o->has_data) goto duplicate; + if( load_payload( (unsigned char **)&o->data, &o->data_size, + f, fn, s, err, es)) return( -1 ); + o->data_align = s->align; + o->has_data = 1; + return( 0 ); + case CPU_OBJ_SEC_BSS: + if (o->has_bss) goto duplicate; + if (s->size > SIZE_MAX) + { + snprintf( err, es, ".bss too large for this host" ); + return( -1 ); + } + o->bss_size = (size_t)s->size; + o->bss_align = s->align; + o->has_bss = 1; + return( 0 ); + case CPU_OBJ_SEC_CONTROL: + if (o->has_control) goto duplicate; + if( load_payload( (unsigned char **)&o->control, &o->control_size, + f, fn, s, err, es)) return( -1 ); + o->control_align = s->align; + o->has_control = 1; + return( 0 ); + case CPU_OBJ_SEC_INPUT: + if (o->has_input) goto duplicate; + if( load_payload( (unsigned char **)&o->input, &o->input_size, + f, fn, s, err, es)) return( -1 ); + o->input_align = s->align; + o->has_input = 1; + return( 0 ); + case CPU_OBJ_SEC_OUTPUT: + if (o->has_output) goto duplicate; + if( load_payload( (unsigned char **)&o->output, &o->output_size, + f, fn, s, err, es)) return( -1 ); + o->output_align = s->align; + o->has_output = 1; + return( 0 ); + case CPU_OBJ_SEC_TRACE: + if (o->has_trace) goto duplicate; + if( load_payload( (unsigned char **)&o->trace, &o->trace_size, + f, fn, s, err, es)) return( -1 ); + o->trace_align = s->align; + o->has_trace = 1; + return( 0 ); + default: + snprintf( err, es, "unsupported allocated section" ); + return( -1 ); + } + +duplicate: + snprintf( err, es, "duplicate section %s", sec_name( l ) ); + return( -1 ); +} + +static int +cpu_object_read_data_internal( const unsigned char *f, size_t fn, + cpu_asm_object *o, char *err, size_t es ) +{ + int data; + __mpu_uint64_t shoff; + __mpu_uint16_t shnum, shentsz, shstrndx; + size_t i, j; + rsh *sh = NULL; + const unsigned char *shstr = NULL; + size_t shstrn = 0; + unsigned *logical = NULL; + int target_found = 0; + __mpu_uint32_t symsec = 0; + MElf_McpuTarget mt; + __mpu_uint32_t *smap = NULL; + size_t symcount = 0; + int rc = -1; + + if( !f || !o ) + return( -1 ); + memset( o, 0, sizeof( *o ) ); + + if( validate_ident( f, fn, MELF_ET_REL, &data, err, es ) ) + goto out; + + if( cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, data ) != 0 || + cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, data ) != 0 || + cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, data ) != 0) + { + snprintf( err, es, "ET_REL contains executable/program-header state" ); + goto out; + } + + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, data ); + shentsz = cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, data ); + shstrndx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + if (shentsz != MELF64_SHDR_SIZE || !shnum || shstrndx == MELF_SHN_UNDEF || + shstrndx >= shnum || + !cpu_melf_span( fn, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE )) + { + snprintf( err, es, "bad MELF section table" ); + goto out; + } + + sh = ( rsh * ) calloc( shnum, sizeof( *sh ) ); + logical = (unsigned *)malloc( shnum *sizeof( *logical ) ); + if (!sh || !logical) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < shnum; ++i ) + { + get_shdr64( f + (size_t)shoff + i *MELF64_SHDR_SIZE, data, &sh[i] ); + logical[i] = CPU_OBJ_SEC_UNDEF; + } + if( !shdr_is_zero( &sh[0] ) ) + { + snprintf( err, es, "section header zero is not all-zero" ); + goto out; + } + + if (sh[shstrndx].type != MELF_SHT_STRTAB || sh[shstrndx].flags != 0 || + sh[shstrndx].addr != 0 || sh[shstrndx].link != 0 || + sh[shstrndx].info != 0 || sh[shstrndx].align != 1 || + sh[shstrndx].entsize != 0 || sh[shstrndx].size == 0 || + sh[shstrndx].size > SIZE_MAX || + !cpu_melf_span( fn, sh[shstrndx].off, sh[shstrndx].size )) + { + snprintf( err, es, "bad .shstrtab" ); + goto out; + } + shstr = f + (size_t)sh[shstrndx].off; + shstrn = (size_t)sh[shstrndx].size; + if (shstr[0] != 0) + { + snprintf( err, es, ".shstrtab does not start with NUL" ); + goto out; + } + + for( i = 1; i < shnum; ++i ) + { + const char *name; + + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( i == shstrndx && strcmp( name, MELF_SEC_SHSTRTAB ) ) + { + snprintf( err, es, "section-name table has noncanonical name" ); + goto out; + } + + if ((sh[i].type == MELF_SHT_PROGBITS || sh[i].type == MELF_SHT_NOBITS) && + (sh[i].flags & MELF_SHF_ALLOC)) + { + unsigned l; + + if( map_name_logical( name, &l ) ) + { + snprintf( err, es, "unsupported allocated MELF section: %s", name ); + goto out; + } + logical[i] = l; + if ((l == CPU_OBJ_SEC_BSS) != (sh[i].type == MELF_SHT_NOBITS)) + { + snprintf( err, es, "section type disagrees with section role: %s", name ); + goto out; + } + if( sh[i].flags != sec_flags( l ) || sh[i].addr != 0 || + sh[i].link != 0 || sh[i].info != 0 || sh[i].entsize != 0 || + sh[i].align == 0 || !cpu_melf_is_pow2( sh[i].align )) + { + snprintf( err, es, "invalid ET_REL section attributes: %s", name ); + goto out; + } + if (sh[i].type != MELF_SHT_NOBITS && + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "bad section file range: %s", name ); + goto out; + } + if( load_allocated_section( o, l, &sh[i], f, fn, err, es ) ) + goto out; + } + else if (sh[i].type == MELF_SHT_MCPU_TARGET) + { + if (target_found++) + { + snprintf( err, es, "duplicate .mcpu.target" ); + goto out; + } + if( strcmp( name, MELF_SEC_MCPU_TARGET ) || sh[i].flags != 0 || + sh[i].addr != 0 || sh[i].size != MELF_MCPU_TARGET_SIZE || + sh[i].link != 0 || sh[i].info != 0 || sh[i].align != 8 || + sh[i].entsize != MELF_MCPU_TARGET_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_target( f + (size_t)sh[i].off, &mt, data ) || + cpu_melf_validate_target( &mt, data, err, es )) + goto out; + } + else if (sh[i].type == MELF_SHT_SYMTAB) + { + if( strcmp( name, MELF_SEC_SYMTAB ) || symsec ) + { + snprintf( err, es, "multiple/noncanonical symbol table" ); + goto out; + } + symsec = (__mpu_uint32_t)i; + } + } + + if (!target_found) + { + snprintf( err, es, "missing .mcpu.target" ); + goto out; + } + if (!symsec) + { + snprintf( err, es, "missing .symtab" ); + goto out; + } + + { + rsh *sy = &sh[symsec]; + const unsigned char *str; + size_t strn; + const char *strname; + + if (sy->flags != 0 || sy->addr != 0 || sy->align != 8 || + sy->entsize != MELF64_SYM_SIZE || sy->size % MELF64_SYM_SIZE || + sy->link == 0 || sy->link >= shnum || sy->info == 0 || + sy->info > sy->size / MELF64_SYM_SIZE || + sh[sy->link].type != MELF_SHT_STRTAB || + sh[sy->link].flags != 0 || sh[sy->link].addr != 0 || + sh[sy->link].link != 0 || sh[sy->link].info != 0 || + sh[sy->link].align != 1 || sh[sy->link].entsize != 0 || + !cpu_melf_span( fn, sy->off, sy->size ) || + !cpu_melf_span( fn, sh[sy->link].off, sh[sy->link].size ) || + sh[sy->link].size == 0 || sh[sy->link].size > SIZE_MAX) + { + snprintf( err, es, "bad MELF symbol table" ); + goto out; + } + if( name_at( shstr, shstrn, sh[sy->link].name, &strname ) || + strcmp( strname, MELF_SEC_STRTAB )) + { + snprintf( err, es, "noncanonical symbol string table" ); + goto out; + } + + str = f + (size_t)sh[sy->link].off; + strn = (size_t)sh[sy->link].size; + if (str[0] != 0) + { + snprintf( err, es, ".strtab does not start with NUL" ); + goto out; + } + + symcount = (size_t)(sy->size / MELF64_SYM_SIZE); + if (symcount == 0) + { + snprintf( err, es, "empty MELF symbol table" ); + goto out; + } + { + const unsigned char *q0 = f + (size_t)sy->off; + size_t k; + for( k = 0; k < MELF64_SYM_SIZE; ++k ) + if (q0[k] != 0) + { + snprintf( err, es, "symbol table entry zero is not all-zero" ); + goto out; + } + } + + smap = (__mpu_uint32_t *)malloc( symcount *sizeof( *smap ) ); + if (!smap) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < symcount; ++i ) + smap[i] = UINT32_MAX; + + for( i = 1; i < symcount; ++i ) + { + const unsigned char *q = f + (size_t)sy->off + i * MELF64_SYM_SIZE; + __mpu_uint32_t no = cpu_melf_get32( q + MELF64_SYM_ST_NAME_OFF, data ); + __mpu_uint16_t shn = cpu_melf_get16( q + MELF64_SYM_ST_SHNDX_OFF, data ); + __mpu_uint64_t value = cpu_melf_get64( q + MELF64_SYM_ST_VALUE_OFF, data ); + __mpu_uint64_t size = cpu_melf_get64( q + MELF64_SYM_ST_SIZE_OFF, data ); + unsigned bind = MELF64_ST_BIND( q[MELF64_SYM_ST_INFO_OFF] ); + unsigned type = MELF64_ST_TYPE( q[MELF64_SYM_ST_INFO_OFF] ); + unsigned b, t; + const char *n; + + if (i < sy->info) + { + if (bind != MELF_STB_LOCAL) + { + snprintf( err, es, "non-local symbol precedes sh_info boundary" ); + goto out; + } + } + else if (bind == MELF_STB_LOCAL) + { + snprintf( err, es, "local symbol follows sh_info boundary" ); + goto out; + } + if (q[MELF64_SYM_ST_OTHER_OFF] != MELF_STV_DEFAULT) + { + snprintf( err, es, "unsupported symbol visibility" ); + goto out; + } + if( name_at( str, strn, no, &n ) || !*n ) + { + snprintf( err, es, "bad/empty symbol name" ); + goto out; + } + + if (bind == MELF_STB_LOCAL) + b = CPU_OBJ_BIND_LOCAL; + else if (bind == MELF_STB_GLOBAL) + b = CPU_OBJ_BIND_GLOBAL; + else if (bind == MELF_STB_WEAK) + b = CPU_OBJ_BIND_WEAK; + else + { + snprintf( err, es, "unsupported symbol binding" ); + goto out; + } + + if (type == MELF_STT_NOTYPE) + t = CPU_OBJ_TYPE_NOTYPE; + else if (type == MELF_STT_OBJECT) + t = CPU_OBJ_TYPE_OBJECT; + else if (type == MELF_STT_FUNC) + t = CPU_OBJ_TYPE_FUNC; + else + { + snprintf( err, es, "unsupported symbol type" ); + goto out; + } + + if (shn == MELF_SHN_UNDEF) + { + if (b == CPU_OBJ_BIND_LOCAL || value != 0 || size != 0) + { + snprintf( err, es, "malformed undefined symbol: %s", n ); + goto out; + } + if( object_add_symbol( o, n, CPU_OBJ_SEC_UNDEF, b, t, 0, err, es ) ) + goto out; + } + else + { + if (shn >= MELF_SHN_LORESERVE || shn >= shnum || + logical[shn] == CPU_OBJ_SEC_UNDEF) + { + snprintf( err, es, "symbol refers to unsupported section" ); + goto out; + } + if (value > sh[shn].size || size > sh[shn].size - value) + { + snprintf( err, es, "symbol range outside section: %s", n ); + goto out; + } + if( object_add_symbol( o, n, logical[shn], b, t, value, err, es ) ) + goto out; + o->symbols[o->symbol_count - 1].size = size; + } + smap[i] = (__mpu_uint32_t)(o->symbol_count - 1); + } + } + + for( i = 1; i < shnum; ++i ) + { + if (sh[i].type == MELF_SHT_RELA) + { + const char *name; + unsigned target_logical; + const char *expected_name; + + if (sh[i].link != symsec || sh[i].info == 0 || sh[i].info >= shnum || + logical[sh[i].info] == CPU_OBJ_SEC_UNDEF || + sh[sh[i].info].type == MELF_SHT_NOBITS || + sh[i].flags != 0 || sh[i].addr != 0 || sh[i].align != 8 || + sh[i].entsize != MELF64_RELA_SIZE || + sh[i].size % MELF64_RELA_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "bad RELA section" ); + goto out; + } + + target_logical = logical[sh[i].info]; + expected_name = rela_name( target_logical ); + if( !expected_name || name_at( shstr, shstrn, sh[i].name, &name ) || + strcmp( name, expected_name )) + { + snprintf( err, es, "noncanonical RELA section name" ); + goto out; + } + + for( j = 0; j < (size_t)( sh[i].size / MELF64_RELA_SIZE ); ++j ) + { + const unsigned char *q = f + (size_t)sh[i].off + j * MELF64_RELA_SIZE; + __mpu_uint64_t roff = cpu_melf_get64( q + MELF64_RELA_R_OFFSET_OFF, data ); + __mpu_uint64_t info = cpu_melf_get64( q + MELF64_RELA_R_INFO_OFF, data ); + __mpu_uint32_t ms = MELF64_R_SYM( info ); + __mpu_uint32_t rt = MELF64_R_TYPE( info ); + __mpu_int64_t addend; + + if (ms == MELF_STN_UNDEF || ms >= symcount || smap[ms] == UINT32_MAX || + rt < CPU_RELOC_CODE64 || rt > CPU_RELOC_CPTR64_DATA) + { + snprintf( err, es, "bad RELA symbol/type" ); + goto out; + } + if (roff > sh[sh[i].info].size || + CPU_PTR64_SIZE > sh[sh[i].info].size - roff) + { + snprintf( err, es, "RELA field outside section" ); + goto out; + } + u64_to_s64( cpu_melf_get64( q + MELF64_RELA_R_ADDEND_OFF, data ), &addend ); + if( object_add_reloc( o, target_logical, rt, smap[ms], roff, + addend, err, es)) + goto out; + } + } + } + + if (o->init_size > SIZE_MAX - o->text_size || + o->init_size + o->text_size > SIZE_MAX - o->fini_size) + { + snprintf( err, es, "PROGRAM compatibility image too large" ); + goto out; + } + o->code_size = o->init_size + o->text_size + o->fini_size; + o->code_align = 1; + if (o->code_size) + { + o->code = (__mpu_UNIT_t *)malloc( o->code_size ); + if (!o->code) + { + snprintf( err, es, "out of memory" ); + goto out; + } + memcpy( o->code, o->init, o->init_size ); + memcpy( o->code + o->init_size, o->text, o->text_size ); + memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size ); + } + + rc = 0; +out: + free( sh ); + free( logical ); + free( smap ); + if (rc) + cpu_asm_object_free( o ); + return( rc ); +} + +int +cpu_object_read_data( const void *data, size_t size, cpu_asm_object *o, + char *err, size_t es ) +{ + if( data == NULL || size == 0 || o == NULL ) + return( -1 ); + + return( cpu_object_read_data_internal((const unsigned char *)data, size, + o, err, es) ); + +} /* End of cpu_object_read_data() */ + + +int +cpu_object_read( const char *path, cpu_asm_object *o, char *err, size_t es ) +{ + unsigned char *data = NULL; + size_t size = 0; + int rc; + + if( path == NULL || o == NULL ) + return( -1 ); + + if( cpu_melf_readall(path, &data, &size, err, es) ) + return( -1 ); + + rc = cpu_object_read_data_internal( data, size, o, err, es ); + free( data ); + return( rc ); + +} /* End of cpu_object_read() */ + diff --git a/utils/mcpu-object.h b/utils/mcpu-object.h new file mode 100644 index 0000000..3af938a --- /dev/null +++ b/utils/mcpu-object.h @@ -0,0 +1,12 @@ +#ifndef __CPU_OBJECT_H +#define __CPU_OBJECT_H + +#include "mcpu-asm.h" + +/* MELF ET_REL reader/writer. */ +int cpu_object_write( const char *path, const cpu_asm_object *o, char *err, size_t errsz ); +int cpu_object_read( const char *path, cpu_asm_object *o, char *err, size_t errsz ); +int cpu_object_read_data( const void *data, size_t size, cpu_asm_object *o, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-readelf.c b/utils/mcpu-readelf.c new file mode 100644 index 0000000..bfa95e6 --- /dev/null +++ b/utils/mcpu-readelf.c @@ -0,0 +1,1215 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct __readelf_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} readelf_shdr; + + +typedef struct __readelf_phdr +{ + __mpu_uint32_t type; + __mpu_uint32_t flags; + __mpu_uint64_t offset; + __mpu_uint64_t vaddr; + __mpu_uint64_t paddr; + __mpu_uint64_t filesz; + __mpu_uint64_t memsz; + __mpu_uint64_t maxsz; + __mpu_uint64_t align; +} readelf_phdr; + + +typedef struct __readelf_file +{ + const char *path; + unsigned char *file; + size_t file_size; + int data; + + __mpu_uint16_t type; + __mpu_uint16_t machine; + __mpu_uint32_t version; + __mpu_uint64_t entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + __mpu_uint64_t phoff; + __mpu_uint64_t shoff; + __mpu_uint32_t flags; + __mpu_uint16_t ehsize; + __mpu_uint16_t phentsize; + __mpu_uint16_t phnum; + __mpu_uint16_t shentsize; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + + readelf_phdr *phdr; + readelf_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} readelf_file; + + +typedef struct __readelf_options +{ + int file_header; + int program_headers; + int section_headers; + int symbols; + int relocations; + int dynamic; + int arch_specific; + int wide; +} readelf_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s <option(s)> MELF-file(s)\n\n" + "Display information about LibMCPU MELF ABI1/ABI2/ABI3 files.\n\n" + "Options:\n" + " -a, --all - equivalent to -h -l -S -s -r -d -A\n" + " -e, --headers - equivalent to -h -l -S\n" + " -h, --file-header - display the MELF file header\n" + " -l, --program-headers - display the program headers\n" + " --segments - alias for --program-headers\n" + " -S, --section-headers - display the section headers\n" + " --sections - alias for --section-headers\n" + " -s, --symbols - display symbol tables (.symtab/.dynsym)\n" + " --syms - alias for --symbols\n" + " -r, --relocs - display RELA relocation sections\n" + " -d, --dynamic - display ABI-v3 dynamic dependency metadata\n" + " -A, --arch-specific - display MCPU target/lifecycle metadata\n" + " -W, --wide - do not truncate output (accepted for compatibility)\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static const char * +file_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_ET_NONE: return( "NONE (None)" ); + case MELF_ET_REL: return( "REL (Relocatable file)" ); + case MELF_ET_EXEC: return( "EXEC (Executable file)" ); + case MELF_ET_DYN: return( "DYN (Shared object file)" ); + case MELF_ET_CORE: return( "CORE (Core file)" ); + default: return( "<unknown>" ); + } + +} /* End of file_type_name() */ + + +static const char * +section_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_SHT_NULL: return( "NULL" ); + case MELF_SHT_PROGBITS: return( "PROGBITS" ); + case MELF_SHT_SYMTAB: return( "SYMTAB" ); + case MELF_SHT_STRTAB: return( "STRTAB" ); + case MELF_SHT_RELA: return( "RELA" ); + case MELF_SHT_DYNAMIC: return( "DYNAMIC" ); + case MELF_SHT_NOBITS: return( "NOBITS" ); + case MELF_SHT_DYNSYM: return( "DYNSYM" ); + case MELF_SHT_MCPU_TARGET: return( "MCPU_TARGET" ); + case MELF_SHT_MCPU_LIFECYCLE: return( "MCPU_LIFECYCLE" ); + default: return( "<unknown>" ); + } + +} /* End of section_type_name() */ + + +static const char * +program_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_PT_NULL: return( "NULL" ); + case MELF_PT_LOAD: return( "LOAD" ); + case MELF_PT_MCPU_STACK: return( "MCPU_STACK" ); + case MELF_PT_MCPU_HEAP: return( "MCPU_HEAP" ); + default: return( "<unknown>" ); + } + +} /* End of program_type_name() */ + + +static const char * +symbol_bind_name( unsigned int bind ) +{ + switch( bind ) + { + case MELF_STB_LOCAL: return( "LOCAL" ); + case MELF_STB_GLOBAL: return( "GLOBAL" ); + case MELF_STB_WEAK: return( "WEAK" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_bind_name() */ + + +static const char * +symbol_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_STT_NOTYPE: return( "NOTYPE" ); + case MELF_STT_OBJECT: return( "OBJECT" ); + case MELF_STT_FUNC: return( "FUNC" ); + case MELF_STT_SECTION: return( "SECTION" ); + case MELF_STT_FILE: return( "FILE" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_type_name() */ + + +static const char * +symbol_visibility_name( unsigned int visibility ) +{ + switch( visibility ) + { + case MELF_STV_DEFAULT: return( "DEFAULT" ); + case MELF_STV_INTERNAL: return( "INTERNAL" ); + case MELF_STV_HIDDEN: return( "HIDDEN" ); + case MELF_STV_PROTECTED: return( "PROTECTED" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_visibility_name() */ + + +static const char * +relocation_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_R_MCPU_NONE: return( "R_MCPU_NONE" ); + case MELF_R_MCPU_CODE64: return( "R_MCPU_CODE64" ); + case MELF_R_MCPU_DATA64: return( "R_MCPU_DATA64" ); + case MELF_R_MCPU_PTR64_CODE: return( "R_MCPU_PTR64_CODE" ); + case MELF_R_MCPU_PTR64_DATA: return( "R_MCPU_PTR64_DATA" ); + case MELF_R_MCPU_CPTR64_DATA: return( "R_MCPU_CPTR64_DATA" ); + default: return( "<unknown>" ); + } + +} /* End of relocation_type_name() */ + + +static void +get_shdr( const unsigned char *p, int data, readelf_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +get_phdr( const unsigned char *p, int data, readelf_phdr *ph ) +{ + memset( ph, 0, sizeof( *ph ) ); + ph->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + ph->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + ph->offset = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + ph->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + ph->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + ph->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + ph->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + ph->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + ph->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); + +} /* End of get_phdr() */ + + +static void +readelf_file_free( readelf_file *m ) +{ + if( !m ) + return; + + free( m->phdr ); + free( m->shdr ); + free( m->file ); + memset( m, 0, sizeof( *m ) ); + +} /* End of readelf_file_free() */ + + +static int +readelf_file_open( const char *path, readelf_file *m, char *err, size_t errsz ) +{ + const unsigned char *h; + size_t i; + + memset( m, 0, sizeof( *m ) ); + m->path = path; + + if( cpu_melf_readall( path, &m->file, &m->file_size, err, errsz ) ) + return( -1 ); + + if( m->file_size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "%s: file is too small for a MELF64 header", path ); + goto fail; + } + + h = m->file; + if( h[MELF_EI_MAG0] != MELFMAG0 || h[MELF_EI_MAG1] != MELFMAG1 || + h[MELF_EI_MAG2] != MELFMAG2 || h[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "%s: not a MELF file", path ); + goto fail; + } + + if( h[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "%s: unsupported MELF class %u", path, + (unsigned int)h[MELF_EI_CLASS] ); + goto fail; + } + + m->data = h[MELF_EI_DATA]; + if( m->data != MELF_DATA_LSB && m->data != MELF_DATA_MSB ) + { + snprintf( err, errsz, "%s: invalid MELF data encoding %d", path, m->data ); + goto fail; + } + + m->type = cpu_melf_get16( h + MELF64_EHDR_E_TYPE_OFF, m->data ); + m->machine = cpu_melf_get16( h + MELF64_EHDR_E_MACHINE_OFF, m->data ); + m->version = cpu_melf_get32( h + MELF64_EHDR_E_VERSION_OFF, m->data ); + m->entry = cpu_melf_get64( h + MELF64_EHDR_E_ENTRY_OFF, m->data ); + m->init_entry = cpu_melf_get64( h + MELF64_EHDR_E_INIT_ENTRY_OFF, m->data ); + m->life_entry = cpu_melf_get64( h + MELF64_EHDR_E_LIFE_ENTRY_OFF, m->data ); + m->fini_entry = cpu_melf_get64( h + MELF64_EHDR_E_FINI_ENTRY_OFF, m->data ); + m->phoff = cpu_melf_get64( h + MELF64_EHDR_E_PHOFF_OFF, m->data ); + m->shoff = cpu_melf_get64( h + MELF64_EHDR_E_SHOFF_OFF, m->data ); + m->flags = cpu_melf_get32( h + MELF64_EHDR_E_FLAGS_OFF, m->data ); + m->ehsize = cpu_melf_get16( h + MELF64_EHDR_E_EHSIZE_OFF, m->data ); + m->phentsize = cpu_melf_get16( h + MELF64_EHDR_E_PHENTSIZE_OFF, m->data ); + m->phnum = cpu_melf_get16( h + MELF64_EHDR_E_PHNUM_OFF, m->data ); + m->shentsize = cpu_melf_get16( h + MELF64_EHDR_E_SHENTSIZE_OFF, m->data ); + m->shnum = cpu_melf_get16( h + MELF64_EHDR_E_SHNUM_OFF, m->data ); + m->shstrndx = cpu_melf_get16( h + MELF64_EHDR_E_SHSTRNDX_OFF, m->data ); + + if( m->ehsize != MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported MELF64 header size %u", path, + (unsigned int)m->ehsize ); + goto fail; + } + + if( m->phnum ) + { + if( m->phentsize != MELF64_PHDR_SIZE || + !cpu_melf_span( m->file_size, m->phoff, + (__mpu_uint64_t)m->phnum * MELF64_PHDR_SIZE ) ) + { + snprintf( err, errsz, "%s: malformed program-header table", path ); + goto fail; + } + + m->phdr = (readelf_phdr *)calloc( m->phnum, sizeof( *m->phdr ) ); + if( !m->phdr ) + { + snprintf( err, errsz, "out of memory" ); + goto fail; + } + + for( i = 0; i < m->phnum; ++i ) + get_phdr( m->file + (size_t)m->phoff + i * MELF64_PHDR_SIZE, + m->data, &m->phdr[i] ); + } + else if( m->phentsize != 0 && m->phentsize != MELF64_PHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported program-header entry size %u", path, + (unsigned int)m->phentsize ); + goto fail; + } + + if( m->shnum ) + { + if( m->shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( m->file_size, m->shoff, + (__mpu_uint64_t)m->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "%s: malformed section-header table", path ); + goto fail; + } + + m->shdr = (readelf_shdr *)calloc( m->shnum, sizeof( *m->shdr ) ); + if( !m->shdr ) + { + snprintf( err, errsz, "out of memory" ); + goto fail; + } + + for( i = 0; i < m->shnum; ++i ) + get_shdr( m->file + (size_t)m->shoff + i * MELF64_SHDR_SIZE, + m->data, &m->shdr[i] ); + + if( m->shstrndx >= m->shnum || + m->shdr[m->shstrndx].type != MELF_SHT_STRTAB || + !cpu_melf_span( m->file_size, m->shdr[m->shstrndx].offset, + m->shdr[m->shstrndx].size ) ) + { + snprintf( err, errsz, "%s: malformed section-name string table", path ); + goto fail; + } + + m->shstr = m->file + (size_t)m->shdr[m->shstrndx].offset; + m->shstr_size = (size_t)m->shdr[m->shstrndx].size; + } + else if( m->shentsize != 0 && m->shentsize != MELF64_SHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported section-header entry size %u", path, + (unsigned int)m->shentsize ); + goto fail; + } + + return( 0 ); + +fail: + readelf_file_free( m ); + return( -1 ); + +} /* End of readelf_file_open() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( "<corrupt>" ); + + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( "<corrupt>" ); + + return( (const char *)p ); + +} /* End of string_at() */ + + +static const char * +section_name( const readelf_file *m, size_t index ) +{ + if( !m->shdr || index >= m->shnum ) + return( "<invalid>" ); + + return( string_at( m->shstr, m->shstr_size, m->shdr[index].name ) ); + +} /* End of section_name() */ + + +static int +section_payload( const readelf_file *m, size_t index, + const unsigned char **payload, size_t *size ) +{ + const readelf_shdr *sh; + + if( !m->shdr || index >= m->shnum ) + return( -1 ); + + sh = &m->shdr[index]; + if( sh->type == MELF_SHT_NOBITS ) + { + *payload = NULL; + *size = (size_t)sh->size; + return( 0 ); + } + + if( sh->size > SIZE_MAX || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + return( -1 ); + + *payload = m->file + (size_t)sh->offset; + *size = (size_t)sh->size; + return( 0 ); + +} /* End of section_payload() */ + + +static void +print_file_header( const readelf_file *m ) +{ + const unsigned char *h = m->file; + size_t i; + + printf( "MELF Header:\n" ); + printf( " Magic: " ); + for( i = 0; i < MELF_EI_NIDENT; ++i ) + printf( " %02x", (unsigned int)h[i] ); + printf( "\n" ); + printf( " Class: MELF64\n" ); + printf( " Data: %s endian\n", + m->data == MELF_DATA_LSB ? "little" : "big" ); + printf( " Version: %u\n", + (unsigned int)h[MELF_EI_VERSION] ); + printf( " OS/ABI: %s\n", + h[MELF_EI_OSABI] == MELF_OSABI_MCPU ? "MCPU" : "<unknown>" ); + printf( " ABI Version: %u\n", + (unsigned int)h[MELF_EI_ABIVERSION] ); + printf( " Type: %s\n", file_type_name( m->type ) ); + printf( " Machine: %s (0x%04x)\n", + m->machine == MELF_EM_MCPU ? "MCPU" : "<unknown>", + (unsigned int)m->machine ); + printf( " Version: 0x%x\n", (unsigned int)m->version ); + printf( " Entry point address: 0x%016" PRIx64 "\n", + (uint64_t)m->entry ); + printf( " INIT entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->init_entry ); + printf( " LIFE entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->life_entry ); + printf( " FINI entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->fini_entry ); + printf( " Start of program headers: %" PRIu64 " (bytes into file)\n", + (uint64_t)m->phoff ); + printf( " Start of section headers: %" PRIu64 " (bytes into file)\n", + (uint64_t)m->shoff ); + printf( " Flags: 0x%x\n", (unsigned int)m->flags ); + printf( " Size of this header: %u (bytes)\n", + (unsigned int)m->ehsize ); + printf( " Size of program headers: %u (bytes)\n", + (unsigned int)m->phentsize ); + printf( " Number of program headers: %u\n", (unsigned int)m->phnum ); + printf( " Size of section headers: %u (bytes)\n", + (unsigned int)m->shentsize ); + printf( " Number of section headers: %u\n", (unsigned int)m->shnum ); + printf( " Section header string table index: %u\n", (unsigned int)m->shstrndx ); + +} /* End of print_file_header() */ + + +static void +print_program_headers( const readelf_file *m ) +{ + size_t i; + + if( !m->phnum ) + { + printf( "There are no program headers in this file.\n" ); + return; + } + + printf( "There are %u program headers, starting at offset %" PRIu64 ":\n\n", + (unsigned int)m->phnum, (uint64_t)m->phoff ); + printf( "Program Headers:\n" ); + printf( " Type Offset VirtAddr PhysAddr\n" ); + printf( " FileSiz MemSiz MaxSiz Flg Align\n" ); + + for( i = 0; i < m->phnum; ++i ) + { + const readelf_phdr *ph = &m->phdr[i]; + char flags[4]; + + flags[0] = (ph->flags & MELF_PF_R) ? 'R' : ' '; + flags[1] = (ph->flags & MELF_PF_W) ? 'W' : ' '; + flags[2] = (ph->flags & MELF_PF_X) ? 'E' : ' '; + flags[3] = '\0'; + + printf( " %-13s 0x%016" PRIx64 " 0x%016" PRIx64 " 0x%016" PRIx64 "\n", + program_type_name( ph->type ), (uint64_t)ph->offset, + (uint64_t)ph->vaddr, (uint64_t)ph->paddr ); + printf( " 0x%016" PRIx64 " 0x%016" PRIx64 " 0x%016" PRIx64 + " %3s 0x%" PRIx64 "\n", + (uint64_t)ph->filesz, (uint64_t)ph->memsz, (uint64_t)ph->maxsz, + flags, (uint64_t)ph->align ); + } + +} /* End of print_program_headers() */ + + +static void +section_flags( __mpu_uint64_t flags, char out[8] ) +{ + size_t n = 0; + + if( flags & MELF_SHF_WRITE ) + out[n++] = 'W'; + if( flags & MELF_SHF_ALLOC ) + out[n++] = 'A'; + if( flags & MELF_SHF_EXECINSTR ) + out[n++] = 'X'; + if( MELF_SHF_MCPU_HAS_SEG( flags ) ) + { + unsigned int seg = MELF_SHF_MCPU_GET_SEG( flags ); + + if( seg <= MELF_MCPU_SEG_HEAP ) + { + switch( seg ) + { + case MELF_MCPU_SEG_DATA: out[n++] = 'D'; break; + case MELF_MCPU_SEG_STACK: out[n++] = 'S'; break; + case MELF_MCPU_SEG_PROGRAM: out[n++] = 'P'; break; + case MELF_MCPU_SEG_CONTROL: out[n++] = 'C'; break; + case MELF_MCPU_SEG_INPUT: out[n++] = 'I'; break; + case MELF_MCPU_SEG_OUTPUT: out[n++] = 'O'; break; + case MELF_MCPU_SEG_TRACE: out[n++] = 'T'; break; + case MELF_MCPU_SEG_HEAP: out[n++] = 'H'; break; + } + } + } + out[n] = '\0'; + +} /* End of section_flags() */ + + +static void +print_section_headers( const readelf_file *m ) +{ + size_t i; + + if( !m->shnum ) + { + printf( "There are no section headers in this file.\n" ); + return; + } + + printf( "There are %u section headers, starting at offset 0x%" PRIx64 ":\n\n", + (unsigned int)m->shnum, (uint64_t)m->shoff ); + printf( "Section Headers:\n" ); + printf( " [Nr] Name Type Address Offset " + "Size EntSize Flags Link Info Align\n" ); + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + char flags[8]; + + section_flags( sh->flags, flags ); + printf( " [%2zu] %-20s %-16s %016" PRIx64 " %08" PRIx64 + " %016" PRIx64 " %016" PRIx64 " %-5s %4u %4u %5" PRIu64 "\n", + i, section_name( m, i ), section_type_name( sh->type ), + (uint64_t)sh->addr, (uint64_t)sh->offset, + (uint64_t)sh->size, (uint64_t)sh->entsize, flags, + (unsigned int)sh->link, (unsigned int)sh->info, + (uint64_t)sh->align ); + } + + printf( "Key to Flags: W (write), A (alloc), X (execute), " + "D/S/P/C/I/O/T/H (MCPU segment)\n" ); + +} /* End of print_section_headers() */ + + +static int +get_symbol( const readelf_file *m, size_t table_index, size_t symbol_index, + __mpu_uint32_t *name, unsigned int *bind, unsigned int *type, + unsigned int *visibility, __mpu_uint16_t *shndx, + __mpu_uint64_t *value, __mpu_uint64_t *size ) +{ + const readelf_shdr *sh; + const unsigned char *p; + __mpu_uint8_t info; + __mpu_uint8_t other; + __mpu_uint64_t count; + + if( table_index >= m->shnum ) + return( -1 ); + sh = &m->shdr[table_index]; + if( sh->type != MELF_SHT_SYMTAB && sh->type != MELF_SHT_DYNSYM ) + return( -1 ); + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE ) + return( -1 ); + count = sh->size / MELF64_SYM_SIZE; + if( symbol_index >= count || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + return( -1 ); + + p = m->file + (size_t)sh->offset + symbol_index * MELF64_SYM_SIZE; + *name = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, m->data ); + info = p[MELF64_SYM_ST_INFO_OFF]; + other = p[MELF64_SYM_ST_OTHER_OFF]; + *bind = MELF64_ST_BIND( info ); + *type = MELF64_ST_TYPE( info ); + *visibility = MELF_ST_VISIBILITY( other ); + *shndx = cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, m->data ); + *value = cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, m->data ); + *size = cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, m->data ); + return( 0 ); + +} /* End of get_symbol() */ + + +static const char * +symbol_name( const readelf_file *m, size_t table_index, __mpu_uint32_t name ) +{ + const readelf_shdr *sh; + const readelf_shdr *str; + const unsigned char *payload; + size_t payload_size; + + if( table_index >= m->shnum ) + return( "<invalid>" ); + sh = &m->shdr[table_index]; + if( sh->link >= m->shnum ) + return( "<invalid>" ); + str = &m->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || + section_payload( m, sh->link, &payload, &payload_size ) ) + return( "<invalid>" ); + + return( string_at( payload, payload_size, name ) ); + +} /* End of symbol_name() */ + + +static void +print_symbol_index( __mpu_uint16_t shndx ) +{ + if( shndx == MELF_SHN_UNDEF ) + printf( "%4s", "UND" ); + else if( shndx == MELF_SHN_ABS ) + printf( "%4s", "ABS" ); + else if( shndx == MELF_SHN_COMMON ) + printf( "%4s", "COM" ); + else + printf( "%4u", (unsigned int)shndx ); + +} /* End of print_symbol_index() */ + + +static void +print_symbols( const readelf_file *m ) +{ + size_t i; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + __mpu_uint64_t count; + size_t j; + + if( sh->type != MELF_SHT_SYMTAB && sh->type != MELF_SHT_DYNSYM ) + continue; + + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + { + printf( "Symbol table '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + count = sh->size / MELF64_SYM_SIZE; + printf( "Symbol table '%s' contains %" PRIu64 " entries:\n", + section_name( m, i ), (uint64_t)count ); + printf( " Num: Value Size Type Bind Vis Ndx Name\n" ); + + for( j = 0; j < count; ++j ) + { + __mpu_uint32_t name; + unsigned int bind; + unsigned int type; + unsigned int visibility; + __mpu_uint16_t shndx; + __mpu_uint64_t value; + __mpu_uint64_t size; + + if( get_symbol( m, i, j, &name, &bind, &type, &visibility, &shndx, + &value, &size ) ) + break; + + printf( "%6zu: %016" PRIx64 " %5" PRIu64 " %-8s %-8s %-9s ", + j, (uint64_t)value, (uint64_t)size, + symbol_type_name( type ), symbol_bind_name( bind ), + symbol_visibility_name( visibility ) ); + print_symbol_index( shndx ); + printf( " %s\n", symbol_name( m, i, name ) ); + } + printf( "\n" ); + } + +} /* End of print_symbols() */ + + +static void +print_relocations( const readelf_file *m ) +{ + size_t i; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + __mpu_uint64_t count; + size_t j; + + if( sh->type != MELF_SHT_RELA ) + continue; + + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) || + sh->link >= m->shnum || + (m->shdr[sh->link].type != MELF_SHT_SYMTAB && + m->shdr[sh->link].type != MELF_SHT_DYNSYM) ) + { + printf( "Relocation section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + count = sh->size / MELF64_RELA_SIZE; + printf( "Relocation section '%s' at offset 0x%" PRIx64 + " contains %" PRIu64 " entries:\n", + section_name( m, i ), (uint64_t)sh->offset, (uint64_t)count ); + printf( " Offset Info Type Sym.Value Sym.Name + Addend\n" ); + + for( j = 0; j < count; ++j ) + { + const unsigned char *p; + __mpu_uint64_t offset; + __mpu_uint64_t info; + __mpu_int64_t addend; + __mpu_uint32_t sym; + __mpu_uint32_t type; + __mpu_uint32_t name = 0; + unsigned int bind = 0; + unsigned int stype = 0; + unsigned int visibility = 0; + __mpu_uint16_t shndx = 0; + __mpu_uint64_t value = 0; + __mpu_uint64_t ssize = 0; + const char *sname = "<invalid>"; + + p = m->file + (size_t)sh->offset + j * MELF64_RELA_SIZE; + offset = cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, m->data ); + info = cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, m->data ); + addend = (__mpu_int64_t)cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, + m->data ); + sym = MELF64_R_SYM( info ); + type = MELF64_R_TYPE( info ); + + if( !get_symbol( m, sh->link, sym, &name, &bind, &stype, &visibility, + &shndx, &value, &ssize ) ) + sname = symbol_name( m, sh->link, name ); + + printf( " %016" PRIx64 " %016" PRIx64 " %-20s %016" PRIx64 + " %s %c 0x%" PRIx64 "\n", + (uint64_t)offset, (uint64_t)info, relocation_type_name( type ), + (uint64_t)value, sname, addend < 0 ? '-' : '+', + (uint64_t)(addend < 0 ? -(__mpu_uint64_t)addend : + (__mpu_uint64_t)addend) ); + (void)bind; + (void)stype; + (void)visibility; + (void)shndx; + (void)ssize; + } + printf( "\n" ); + } + +} /* End of print_relocations() */ + + +static const char * +dynamic_tag_name( __mpu_int64_t tag ) +{ + switch( tag ) + { + case MELF_DT_NULL: return( "NULL" ); + case MELF_DT_NEEDED: return( "NEEDED" ); + case MELF_DT_SONAME: return( "SONAME" ); + default: return( "<unknown>" ); + } + +} /* End of dynamic_tag_name() */ + + +static void +print_dynamic( const readelf_file *m ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + const readelf_shdr *str; + const unsigned char *payload; + const unsigned char *strings; + size_t payload_size; + size_t strings_size; + size_t count; + size_t j; + + if( sh->type != MELF_SHT_DYNAMIC ) + continue; + + printed = 1; + if( sh->link >= m->shnum || + m->shdr[sh->link].type != MELF_SHT_STRTAB || + sh->entsize != MELF64_DYN_SIZE || + sh->size < MELF64_DYN_SIZE || + sh->size % MELF64_DYN_SIZE || + section_payload( m, i, &payload, &payload_size ) || + section_payload( m, sh->link, &strings, &strings_size ) ) + { + printf( "Dynamic section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + str = &m->shdr[sh->link]; + (void)str; + count = payload_size / MELF64_DYN_SIZE; + printf( "Dynamic section '%s' at offset 0x%" PRIx64 + " contains %zu entries:\n", + section_name( m, i ), (uint64_t)sh->offset, count ); + printf( " Tag Type Name/Value\n" ); + + for( j = 0; j < count; ++j ) + { + const unsigned char *dp = payload + j * MELF64_DYN_SIZE; + __mpu_int64_t tag = + (__mpu_int64_t)cpu_melf_get64( dp + MELF64_DYN_D_TAG_OFF, m->data ); + __mpu_uint64_t val = + cpu_melf_get64( dp + MELF64_DYN_D_VAL_OFF, m->data ); + + printf( " 0x%016" PRIx64 " %-20s ", + (uint64_t)tag, dynamic_tag_name( tag ) ); + if( tag == MELF_DT_NEEDED ) + printf( "Shared library: [%s]\n", + string_at( strings, strings_size, val ) ); + else if( tag == MELF_DT_SONAME ) + printf( "Library soname: [%s]\n", + string_at( strings, strings_size, val ) ); + else + printf( "0x%" PRIx64 "\n", (uint64_t)val ); + } + printf( "\n" ); + } + + if( !printed ) + printf( "There is no dynamic section in this file.\n" ); + +} /* End of print_dynamic() */ + + +static void +print_target( const MElf_McpuTarget *t ) +{ + printf( "MCPU target attributes:\n" ); + printf( " metadata version: %u\n", (unsigned int)t->mt_version ); + printf( " metadata size: %u\n", (unsigned int)t->mt_size ); + printf( " ISA revision: %u\n", (unsigned int)t->mt_isa_revision ); + printf( " ABI revision: %u\n", (unsigned int)t->mt_abi_revision ); + printf( " program model: %u\n", (unsigned int)t->mt_program_model ); + printf( " data byte order: %u\n", (unsigned int)t->mt_data_byte_order ); + printf( " data word order: %u\n", (unsigned int)t->mt_data_word_order ); + printf( " integer model: %u\n", (unsigned int)t->mt_integer_model ); + printf( " address model: %u\n", (unsigned int)t->mt_address_model ); + printf( " register model: %u\n", (unsigned int)t->mt_register_model ); + printf( " real model: %u\n", (unsigned int)t->mt_real_model ); + printf( " complex model: %u\n", (unsigned int)t->mt_complex_model ); + printf( " maximum ABI alignment: %u\n", (unsigned int)t->mt_max_align ); + printf( " flags: 0x%08x\n", (unsigned int)t->mt_flags ); + printf( " required features: 0x%016" PRIx64 "\n", + (uint64_t)t->mt_required_features ); + +} /* End of print_target() */ + + +static void +print_lifecycle( const MElf_McpuLifecycle *l ) +{ + printf( "MCPU lifecycle attributes:\n" ); + printf( " metadata version: %u\n", (unsigned int)l->ml_version ); + printf( " metadata size: %u\n", (unsigned int)l->ml_size ); + printf( " lifecycle ABI: %u\n", (unsigned int)l->ml_abi ); + printf( " reserved0: 0x%08x\n", (unsigned int)l->ml_reserved0 ); + printf( " flags: 0x%016" PRIx64 "\n", + (uint64_t)l->ml_flags ); + printf( " reserved1: 0x%016" PRIx64 "\n", + (uint64_t)l->ml_reserved1 ); + +} /* End of print_lifecycle() */ + + +static void +print_arch_specific( const readelf_file *m ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + const unsigned char *payload; + size_t payload_size; + + if( sh->type == MELF_SHT_MCPU_TARGET ) + { + MElf_McpuTarget t; + + if( section_payload( m, i, &payload, &payload_size ) || + payload_size != MELF_MCPU_TARGET_SIZE || + cpu_melf_decode_target( payload, &t, m->data ) ) + { + printf( "MCPU target section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + print_target( &t ); + printf( "\n" ); + printed = 1; + } + else if( sh->type == MELF_SHT_MCPU_LIFECYCLE ) + { + MElf_McpuLifecycle l; + + if( section_payload( m, i, &payload, &payload_size ) || + payload_size != MELF_MCPU_LIFECYCLE_SIZE || + cpu_melf_decode_lifecycle( payload, &l, m->data ) ) + { + printf( "MCPU lifecycle section '%s' is malformed.\n", + section_name( m, i ) ); + continue; + } + print_lifecycle( &l ); + printf( "\n" ); + printed = 1; + } + } + + if( !printed ) + printf( "There are no MCPU architecture-specific metadata sections in this file.\n" ); + +} /* End of print_arch_specific() */ + + +static int +parse_short_options( const char *arg, readelf_options *opt ) +{ + const char *p; + + for( p = arg + 1; *p; ++p ) + { + switch( *p ) + { + case 'a': + opt->file_header = 1; + opt->program_headers = 1; + opt->section_headers = 1; + opt->symbols = 1; + opt->relocations = 1; + opt->dynamic = 1; + opt->arch_specific = 1; + break; + case 'e': + opt->file_header = 1; + opt->program_headers = 1; + opt->section_headers = 1; + break; + case 'h': opt->file_header = 1; break; + case 'l': opt->program_headers = 1; break; + case 'S': opt->section_headers = 1; break; + case 's': opt->symbols = 1; break; + case 'r': opt->relocations = 1; break; + case 'd': opt->dynamic = 1; break; + case 'A': opt->arch_specific = 1; break; + case 'W': opt->wide = 1; break; + default: return( -1 ); + } + } + + return( 0 ); + +} /* End of parse_short_options() */ + + +static int +have_output_option( const readelf_options *opt ) +{ + return( opt->file_header || opt->program_headers || opt->section_headers || + opt->symbols || opt->relocations || opt->dynamic || + opt->arch_specific ); + +} /* End of have_output_option() */ + + +int +main( int argc, char **argv ) +{ + readelf_options opt; + char **files; + size_t file_count = 0; + size_t file_capacity = 0; + int i; + int rc = 0; + + memset( &opt, 0, sizeof( opt ) ); + files = NULL; + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free( files ); + return( 0 ); + } + else if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free( files ); + return( rc == 0 ? 0 : 1 ); + } + else if( !strcmp( arg, "--all" ) ) + parse_short_options( "-a", &opt ); + else if( !strcmp( arg, "--headers" ) ) + parse_short_options( "-e", &opt ); + else if( !strcmp( arg, "--file-header" ) ) + opt.file_header = 1; + else if( !strcmp( arg, "--program-headers" ) || !strcmp( arg, "--segments" ) ) + opt.program_headers = 1; + else if( !strcmp( arg, "--section-headers" ) || !strcmp( arg, "--sections" ) ) + opt.section_headers = 1; + else if( !strcmp( arg, "--symbols" ) || !strcmp( arg, "--syms" ) ) + opt.symbols = 1; + else if( !strcmp( arg, "--relocs" ) ) + opt.relocations = 1; + else if( !strcmp( arg, "--dynamic" ) ) + opt.dynamic = 1; + else if( !strcmp( arg, "--arch-specific" ) ) + opt.arch_specific = 1; + else if( !strcmp( arg, "--wide" ) ) + opt.wide = 1; + else if( arg[0] == '-' && arg[1] != '\0' ) + { + if( parse_short_options( arg, &opt ) ) + { + fprintf( stderr, "mcpu-readelf: unknown option '%s'\n", arg ); + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + } + else + { + if( file_count == file_capacity ) + { + size_t next = file_capacity ? file_capacity * 2U : 4U; + char **q = (char **)realloc( files, next * sizeof( *q ) ); + + if( !q ) + { + fprintf( stderr, "mcpu-readelf: out of memory\n" ); + free( files ); + return( 1 ); + } + files = q; + file_capacity = next; + } + files[file_count++] = argv[i]; + } + } + + if( !have_output_option( &opt ) || !file_count ) + { + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + + for( i = 0; i < (int)file_count; ++i ) + { + readelf_file m; + char err[512]; + + if( file_count > 1 ) + printf( "\nFile: %s\n", files[i] ); + + if( readelf_file_open( files[i], &m, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-readelf: %s\n", err ); + rc = 1; + continue; + } + + if( opt.file_header ) + { + print_file_header( &m ); + if( opt.program_headers || opt.section_headers || opt.symbols || + opt.relocations || opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.program_headers ) + { + print_program_headers( &m ); + if( opt.section_headers || opt.symbols || opt.relocations || + opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.section_headers ) + { + print_section_headers( &m ); + if( opt.symbols || opt.relocations || opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.symbols ) + print_symbols( &m ); + if( opt.relocations ) + print_relocations( &m ); + if( opt.dynamic ) + print_dynamic( &m ); + if( opt.arch_specific ) + print_arch_specific( &m ); + + readelf_file_free( &m ); + } + + free( files ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-run-options.c b/utils/mcpu-run-options.c new file mode 100644 index 0000000..f3cb881 --- /dev/null +++ b/utils/mcpu-run-options.c @@ -0,0 +1,1169 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <libmpuio.h> + +#include "mcpu-run-options.h" + +void +mcpu_run_usage( const mcpu_run_options *options ) +{ + const char *name = options ? options->common.progname : "mcpu-run"; + + fprintf( stderr, + "usage: %s [runtime-options] -- executable [program-args...]\n" + " %s [runtime-options] executable [program-args...] (compatibility)\n\n" + "Execute a LibMCPU MELF ET_EXEC image. STACK and HEAP sizes are part of\n" + "the executable ABI and cannot be overridden by mcpu-run.\n\n" + "MCPU system options:\n" + " --no-config - do not read mcpu-ld.conf files\n" + " --sys-root=PATH - use PATH as MCPU root and do not read config files\n" + " --dump-config - print effective MCPU configuration and exit\n" + " --print-search-dirs - print effective library search directories and exit\n" + "\nLibrary options (must precede executable or explicit --):\n" + " --library-path=DIR[:DIR...] - add explicit library search directories\n" + " --ldpreload=<a.so:b.so> - preload ABI-v2/ABI-v3 shared objects; names use search path\n" + "\nEnvironment:\n" + " MCPU_LD_PRELOAD=a.so:b.so - preload shared objects before command-line --ldpreload\n" + "\nRuntime options (must precede executable or explicit --):\n" + " --int-reg=r<bits>@<addr> - print a signed integer register after stop\n" + " --real-reg=r<bits>@<addr> - print a real register after stop\n" + " --int-data=<bits>@<addr> - print a signed integer object from DATA memory\n" + " --real-data=<bits>@<addr> - print a real object from DATA memory\n" + " --raw-data=<bytes>@<addr> - dump bytes from .data, clipped at .data end\n" + " --int-control=<bits>@<addr> - print a signed integer object from CONTROL memory\n" + " --real-control=<bits>@<addr> - print a real object from CONTROL memory\n" + " --raw-control=<bytes>@<addr> - dump bytes from .control, clipped at .control end\n" + " --int-heap=<bits>@<addr> - print a signed integer object from HEAP memory\n" + " --real-heap=<bits>@<addr> - print a real object from HEAP memory\n" + " --raw-heap=<bytes>@<addr> - dump bytes from HEAP, clipped at HEAP end\n" + " --int-input=<bits>@<addr> - print a signed integer object from INPUT memory\n" + " --real-input=<bits>@<addr> - print a real object from INPUT memory\n" + " --raw-input=<bytes>@<addr> - dump bytes from .input, clipped at .input end\n" + " --int-output=<bits>@<addr> - print a signed integer object from OUTPUT memory\n" + " --real-output=<bits>@<addr> - print a real object from OUTPUT memory\n" + " --raw-output=<bytes>@<addr> - dump bytes from .output, clipped at .output end\n" + " --int-trace=<bits>@<addr> - print a signed integer object from TRACE memory\n" + " --real-trace=<bits>@<addr> - print a real object from TRACE memory\n" + " --raw-trace=<bytes>@<addr> - dump bytes from .trace, clipped at .trace end\n" + " --before-run=<action> - execute host action before first guest instruction\n" + " --after-init=<action> - execute host action after INIT (or empty INIT stage)\n" + " --on-step=<action> - execute host action after every LIFE END boundary\n" + " --after-run=<action> - execute host action after final execution stop\n" + " --digits=<n> - mantissa digits for real output (default: 80)\n" + " --init-only - execute INIT through END, then stop\n" + " --steps=<n> - stop after n LIFE END boundaries\n" + " (default: stop at first LIFE END)\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n" + "\nBare preload names from MCPU_LD_PRELOAD and --ldpreload are searched in\n" + "--library-path order and then in the effective MCPU system library path.\n" + "Names containing '/' are explicit pathnames and bypass name lookup.\n" + "Environment preloads are ordered before command-line preloads; because the\n" + "rightmost equal-strength preload wins, command-line preloads override the\n" + "environment. Empty preload/path-list elements are errors.\n\n" + "Stage actions:\n" + " print-int-regs:r<bits>@<addr>[,...]\n" + " print-real-regs:r<bits>@<addr>[,...]\n" + " set-int-regs:r<bits>@<addr>=<value>[,...]\n" + " set-real-regs:r<bits>@<addr>=<value>[,...]\n" + " print-int-<memory>:<bits>@<addr>[,...]\n" + " print-real-<memory>:<bits>@<addr>[,...]\n" + " set-int-<memory>:<bits>@<addr>=<value>[,...]\n" + " set-real-<memory>:<bits>@<addr>=<value>[,...]\n" + " print-raw-<memory>:<bytes>@<addr>\n\n" + "<memory> is data, control, heap, input, output or trace. Scalar print\n" + "actions print their comma-separated objects on one line separated by spaces.\n" + "Raw actions contain exactly one dump; repeat the stage option for more dumps.\n" + "There are no set-raw-* actions. Values for set-int-* and set-real-* are\n" + "parsed by LibMPUIO mpu_sscanf(). Repeated stage options execute in command-\n" + "line order. Step-number filters are not implemented.\n\n" + "For --raw-* and print-raw-* <bytes>, K/KiB, M/MiB and G/GiB suffixes are\n" + "accepted. K, M and G use binary multipliers (1024, 1024^2 and 1024^3).\n" + "Use -- to unambiguously separate mcpu-run options from executable/argv.\n", + name, name ); + +} /* End of mcpu_run_usage() */ + + +static int +valid_int_bits( unsigned int bits ) +{ + return( bits >= CPU_REGISTER_MIN_BITS && + bits <= CPU_REGISTER_MAX_BITS && + (bits & (bits - 1U)) == 0U ); + +} /* End of valid_int_bits() */ + + +static int +valid_real_bits( unsigned int bits ) +{ + return( bits >= 32U && + bits <= CPU_REGISTER_MAX_BITS && + bits <= (unsigned int)MPU_REAL_IO_LIMIT && + (bits & (bits - 1U)) == 0U ); + +} /* End of valid_real_bits() */ + + +static int +parse_u64( const char *s, __mpu_uint64_t *v ) +{ + char *e; + unsigned long long x; + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s || *e ) return( -1 ); + + *v = (__mpu_uint64_t)x; + + return( 0 ); + +} /* End of parse_u64() */ + + +static int +parse_size( const char *s, __mpu_uint64_t *v ) +{ + unsigned long long x; + __mpu_uint64_t m = 1; + char *e; + + if( !s || !*s || *s == '-' ) return( -1 ); + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s ) return( -1 ); + + if( *e == '\0' ) + m = 1; + else if( !strcmp( e, "K" ) || !strcmp( e, "KiB" ) ) + m = __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "M" ) || !strcmp( e, "MiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "G" ) || !strcmp( e, "GiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else + return( -1 ); + + if( (__mpu_uint64_t)x != x || (__mpu_uint64_t)x > UINT64_MAX / m ) + return( -1 ); + + *v = (__mpu_uint64_t)x * m; + + return( 0 ); + +} /* End of parse_size() */ + + + +static int +parse_integer_register( const char *s, print_spec *spec ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s || (s[0] != 'r' && s[0] != 'R') ) return( -1 ); + + at = strchr( s + 1, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - (s + 1)); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s + 1, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U || + !valid_int_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) || + address > CPU_REGISTER_ADDRESS_MAX || + !__cpu_reg_valid( (unsigned)bits, (__mpu_uint32_t)address ) ) + return( -1 ); + + spec->kind = PRINT_INTEGER_REGISTER; + spec->memory = MEMORY_NONE; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_integer_register() */ + + +static int +parse_real_register( const char *s, print_spec *spec ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s || (s[0] != 'r' && s[0] != 'R') ) return( -1 ); + + at = strchr( s + 1, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - (s + 1)); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s + 1, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U || + !valid_real_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) || + address > CPU_REGISTER_ADDRESS_MAX || + !__cpu_reg_valid( (unsigned)bits, (__mpu_uint32_t)address ) ) + return( -1 ); + + spec->kind = PRINT_REAL_REGISTER; + spec->memory = MEMORY_NONE; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_real_register() */ + + +static int +parse_memory_object( const char *s, print_spec *spec, + int real_object, memory_kind memory ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s ) return( -1 ); + if( real_object && (*s == 'r' || *s == 'R') ) ++s; + + at = strchr( s, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - s); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U ) + return( -1 ); + + if( real_object ? !valid_real_bits( (unsigned)bits ) : + !valid_int_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) ) return( -1 ); + + spec->kind = real_object ? PRINT_REAL_MEMORY : PRINT_INTEGER_MEMORY; + spec->memory = memory; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_memory_object() */ + + +static int +parse_raw_memory( const char *s, print_spec *spec, memory_kind memory ) +{ + const char *at; + char countbuf[32]; + size_t n; + __mpu_uint64_t count, address; + + if( !s ) return( -1 ); + + at = strchr( s, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - s); + if( !n || n >= sizeof( countbuf ) ) return( -1 ); + + memcpy( countbuf, s, n ); + countbuf[n] = 0; + + if( parse_size( countbuf, &count ) || !count || + parse_u64( at + 1, &address ) ) + return( -1 ); + + spec->kind = PRINT_RAW_MEMORY; + spec->memory = memory; + spec->address = address; + spec->count = count; + + return( 0 ); + +} /* End of parse_raw_memory() */ + + +static void +free_stage_action( stage_action *action ) +{ + size_t i; + + if( !action ) return; + + for( i = 0; i < action->count; ++i ) + free( action->item[i].value ); + free( action->item ); + memset( action, 0, sizeof( *action ) ); + +} /* End of free_stage_action() */ + + +static void +free_stage_action_list( stage_action_list *list ) +{ + size_t i; + + if( !list ) return; + + for( i = 0; i < list->count; ++i ) + free_stage_action( &list->action[i] ); + free( list->action ); + memset( list, 0, sizeof( *list ) ); + +} /* End of free_stage_action_list() */ + + +static int +append_stage_action( stage_action_list *list, stage_action *action ) +{ + stage_action *a; + size_t capacity; + + if( list->count == list->capacity ) + { + capacity = list->capacity ? list->capacity * 2U : 4U; + if( capacity < list->capacity || capacity > SIZE_MAX / sizeof( *a ) ) + return( -1 ); + + a = (stage_action *)realloc( list->action, capacity * sizeof( *a ) ); + if( !a ) return( -1 ); + + list->action = a; + list->capacity = capacity; + } + + list->action[list->count++] = *action; + memset( action, 0, sizeof( *action ) ); + + return( 0 ); + +} /* End of append_stage_action() */ + + +static int +append_action_item( stage_action *action, const action_item *item ) +{ + action_item *p; + + if( action->count == SIZE_MAX / sizeof( *p ) ) return( -1 ); + + p = (action_item *)realloc( action->item, + (action->count + 1U) * sizeof( *p ) ); + if( !p ) return( -1 ); + + action->item = p; + action->item[action->count++] = *item; + + return( 0 ); + +} /* End of append_action_item() */ + + +static char * +dup_range( const char *s, size_t n ) +{ + char *p; + + p = (char *)malloc( n + 1U ); + if( !p ) return( (char *)0 ); + + memcpy( p, s, n ); + p[n] = 0; + + return( p ); + +} /* End of dup_range() */ + + +static int +parse_action_object( const char *text, size_t length, + int is_register, int real_object, int set_object, + memory_kind memory, action_item *item ) +{ + print_spec spec; + char *copy, *eq; + const char *value = (const char *)0; + + memset( item, 0, sizeof( *item ) ); + memset( &spec, 0, sizeof( spec ) ); + + copy = dup_range( text, length ); + if( !copy ) return( -1 ); + + eq = strchr( copy, '=' ); + if( set_object ) + { + if( !eq || eq == copy || !eq[1] || strchr( eq + 1, '=' ) ) + goto fail; + *eq = 0; + value = eq + 1; + } + else if( eq ) + goto fail; + + if( is_register ) + { + if( real_object ? parse_real_register( copy, &spec ) : + parse_integer_register( copy, &spec ) ) + goto fail; + } + else + { + if( parse_memory_object( copy, &spec, real_object, memory ) ) + goto fail; + } + + item->bits = spec.bits; + item->address = spec.address; + if( set_object ) + { + item->value = dup_range( value, strlen( value ) ); + if( !item->value ) goto fail; + } + + free( copy ); + return( 0 ); + +fail: + free( item->value ); + memset( item, 0, sizeof( *item ) ); + free( copy ); + return( -1 ); + +} /* End of parse_action_object() */ + + +typedef struct __action_prefix +{ + const char *name; + action_kind kind; + memory_kind memory; + int is_register; + int real_object; + int set_object; + int raw_object; +} action_prefix; + + +static int +parse_stage_action( const char *text, stage_action *action ) +{ + static const action_prefix prefix[] = + { + { "print-int-regs:", ACTION_PRINT_INTEGER_REGISTER, MEMORY_NONE, 1, 0, 0, 0 }, + { "print-real-regs:", ACTION_PRINT_REAL_REGISTER, MEMORY_NONE, 1, 1, 0, 0 }, + { "set-int-regs:", ACTION_SET_INTEGER_REGISTER, MEMORY_NONE, 1, 0, 1, 0 }, + { "set-real-regs:", ACTION_SET_REAL_REGISTER, MEMORY_NONE, 1, 1, 1, 0 }, + + { "print-int-data:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_DATA, 0, 0, 0, 0 }, + { "print-real-data:", ACTION_PRINT_REAL_MEMORY, MEMORY_DATA, 0, 1, 0, 0 }, + { "set-int-data:", ACTION_SET_INTEGER_MEMORY, MEMORY_DATA, 0, 0, 1, 0 }, + { "set-real-data:", ACTION_SET_REAL_MEMORY, MEMORY_DATA, 0, 1, 1, 0 }, + { "print-raw-data:", ACTION_PRINT_RAW_MEMORY, MEMORY_DATA, 0, 0, 0, 1 }, + + { "print-int-control:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_CONTROL, 0, 0, 0, 0 }, + { "print-real-control:", ACTION_PRINT_REAL_MEMORY, MEMORY_CONTROL, 0, 1, 0, 0 }, + { "set-int-control:", ACTION_SET_INTEGER_MEMORY, MEMORY_CONTROL, 0, 0, 1, 0 }, + { "set-real-control:", ACTION_SET_REAL_MEMORY, MEMORY_CONTROL, 0, 1, 1, 0 }, + { "print-raw-control:", ACTION_PRINT_RAW_MEMORY, MEMORY_CONTROL, 0, 0, 0, 1 }, + + { "print-int-heap:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_HEAP, 0, 0, 0, 0 }, + { "print-real-heap:", ACTION_PRINT_REAL_MEMORY, MEMORY_HEAP, 0, 1, 0, 0 }, + { "set-int-heap:", ACTION_SET_INTEGER_MEMORY, MEMORY_HEAP, 0, 0, 1, 0 }, + { "set-real-heap:", ACTION_SET_REAL_MEMORY, MEMORY_HEAP, 0, 1, 1, 0 }, + { "print-raw-heap:", ACTION_PRINT_RAW_MEMORY, MEMORY_HEAP, 0, 0, 0, 1 }, + + { "print-int-input:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_INPUT, 0, 0, 0, 0 }, + { "print-real-input:", ACTION_PRINT_REAL_MEMORY, MEMORY_INPUT, 0, 1, 0, 0 }, + { "set-int-input:", ACTION_SET_INTEGER_MEMORY, MEMORY_INPUT, 0, 0, 1, 0 }, + { "set-real-input:", ACTION_SET_REAL_MEMORY, MEMORY_INPUT, 0, 1, 1, 0 }, + { "print-raw-input:", ACTION_PRINT_RAW_MEMORY, MEMORY_INPUT, 0, 0, 0, 1 }, + + { "print-int-output:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_OUTPUT, 0, 0, 0, 0 }, + { "print-real-output:", ACTION_PRINT_REAL_MEMORY, MEMORY_OUTPUT, 0, 1, 0, 0 }, + { "set-int-output:", ACTION_SET_INTEGER_MEMORY, MEMORY_OUTPUT, 0, 0, 1, 0 }, + { "set-real-output:", ACTION_SET_REAL_MEMORY, MEMORY_OUTPUT, 0, 1, 1, 0 }, + { "print-raw-output:", ACTION_PRINT_RAW_MEMORY, MEMORY_OUTPUT, 0, 0, 0, 1 }, + + { "print-int-trace:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_TRACE, 0, 0, 0, 0 }, + { "print-real-trace:", ACTION_PRINT_REAL_MEMORY, MEMORY_TRACE, 0, 1, 0, 0 }, + { "set-int-trace:", ACTION_SET_INTEGER_MEMORY, MEMORY_TRACE, 0, 0, 1, 0 }, + { "set-real-trace:", ACTION_SET_REAL_MEMORY, MEMORY_TRACE, 0, 1, 1, 0 }, + { "print-raw-trace:", ACTION_PRINT_RAW_MEMORY, MEMORY_TRACE, 0, 0, 0, 1 } + }; + const action_prefix *ap = (const action_prefix *)0; + const char *payload, *p, *comma; + action_item item; + size_t i, n; + + if( !text || !*text ) return( -1 ); + memset( action, 0, sizeof( *action ) ); + + for( i = 0; i < sizeof( prefix ) / sizeof( prefix[0] ); ++i ) + { + n = strlen( prefix[i].name ); + if( !strncmp( text, prefix[i].name, n ) ) + { + ap = &prefix[i]; + payload = text + n; + break; + } + } + + if( !ap || !*payload ) return( -1 ); + + action->kind = ap->kind; + action->memory = ap->memory; + + if( ap->raw_object ) + { + if( strchr( payload, ',' ) || parse_raw_memory( payload, &action->raw, ap->memory ) ) + goto fail; + return( 0 ); + } + + p = payload; + while( *p ) + { + comma = strchr( p, ',' ); + n = comma ? (size_t)(comma - p) : strlen( p ); + if( !n || parse_action_object( p, n, ap->is_register, ap->real_object, + ap->set_object, ap->memory, &item ) ) + goto fail; + + if( append_action_item( action, &item ) ) + { + free( item.value ); + goto fail; + } + + if( !comma ) break; + p = comma + 1; + if( !*p ) goto fail; + } + + return( action->count ? 0 : -1 ); + +fail: + free_stage_action( action ); + return( -1 ); + +} /* End of parse_stage_action() */ + + +static int +parse_stage_option( const char *text, stage_action_list *list ) +{ + stage_action action; + + memset( &action, 0, sizeof( action ) ); + if( parse_stage_action( text, &action ) ) return( -1 ); + + if( append_stage_action( list, &action ) ) + { + free_stage_action( &action ); + return( -1 ); + } + + return( 0 ); + +} /* End of parse_stage_option() */ + + + +void +mcpu_run_options_init( mcpu_run_options *options, const char *progname ) +{ + const char *env_preload; + + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + mcpu_tool_options_init( &options->common, progname ); + options->digits = 80U; + + env_preload = getenv( "MCPU_LD_PRELOAD" ); + if( env_preload != NULL && *env_preload != 0 ) + options->env_ldpreload = env_preload; + +} /* End of mcpu_run_options_init() */ + + +void +mcpu_run_options_free( mcpu_run_options *options ) +{ + if( options == NULL ) + return; + + free_stage_action_list( &options->before_run ); + free_stage_action_list( &options->after_init ); + free_stage_action_list( &options->on_step ); + free_stage_action_list( &options->after_run ); + mcpu_tool_options_free( &options->common ); + +} /* End of mcpu_run_options_free() */ + + +static int +append_resolved_preload( char **list, size_t *length, const char *path ) +{ + size_t path_length; + size_t extra; + char *p; + + if( list == NULL || length == NULL || path == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + path_length = strlen( path ); + extra = path_length + (*length != 0 ? 1U : 0U); + if( *length > SIZE_MAX - extra - 1U ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char *)realloc( *list, *length + extra + 1U ); + if( p == NULL ) + return( -1 ); + + *list = p; + if( *length != 0 ) + p[(*length)++] = ':'; + memcpy( p + *length, path, path_length ); + *length += path_length; + p[*length] = 0; + return( 0 ); + +} /* End of append_resolved_preload() */ + + +static int +resolve_preload_list( const mcpu_run_options *options, const char *list, + const char *source, char **result, + size_t *result_length, char *err, size_t errsz ) +{ + const char *begin; + + if( list == NULL || *list == 0 ) + return( 0 ); + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *name; + char *path = NULL; + + if( length == 0 ) + { + snprintf( err, errsz, "empty name in %s list", source ); + return( -1 ); + } + + name = (char *)malloc( length + 1U ); + if( name == NULL ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + memcpy( name, begin, length ); + name[length] = 0; + if( mcpu_tool_resolve_library(&options->common, name, &path) != 0 ) + { + if( errno == ENOENT && strchr(name, '/') == NULL ) + snprintf( err, errsz, "%s: cannot find MCPU library '%s'", + source, name ); + else + snprintf( err, errsz, "%s: cannot resolve MCPU library '%s': %s", + source, name, strerror(errno) ); + free( name ); + return( -1 ); + } + free( name ); + + if( append_resolved_preload(result, result_length, path) != 0 ) + { + snprintf( err, errsz, "cannot build effective preload list: %s", + strerror(errno) ); + free( path ); + return( -1 ); + } + free( path ); + + if( colon == NULL ) + break; + begin = colon + 1; + } + + return( 0 ); + +} /* End of resolve_preload_list() */ + + +int +mcpu_run_resolve_preload( const mcpu_run_options *options, + char **resolved, char *err, size_t errsz ) +{ + char *result = NULL; + size_t result_length = 0; + + if( options == NULL || resolved == NULL || err == NULL || errsz == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + + if( resolve_preload_list(options, options->env_ldpreload, + "MCPU_LD_PRELOAD", &result, &result_length, + err, errsz) != 0 ) + goto fail; + + if( resolve_preload_list(options, options->ldpreload, + "--ldpreload", &result, &result_length, + err, errsz) != 0 ) + goto fail; + + *resolved = result; + return( 0 ); + +fail: + free( result ); + return( -1 ); + +} /* End of mcpu_run_resolve_preload() */ + + +int +mcpu_run_handle_options( mcpu_run_options *options, int argc, char **argv ) +{ + int i; + int exearg = 0; + + if( options == NULL || argv == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + int common; + + if( options->exe != NULL ) + break; + + if( !strcmp( arg, "--" ) ) + { + if( i + 1 >= argc ) + { + fprintf( stderr, "%s: -- must be followed by an executable\n", + options->common.progname ); + return( -1 ); + } + exearg = ++i; + options->exe = argv[exearg]; + break; + } + else if( !strcmp( arg, "-h" ) || !strcmp( arg, "--help" ) ) + { + options->common.help = 1; + return( 0 ); + } + + common = mcpu_tool_parse_common_option( &options->common, + argc, argv, &i ); + if( common < 0 ) + return( -1 ); + if( common > 0 ) + { + if( options->common.version_action != MCPU_VERSION_NONE ) + return( 0 ); + continue; + } + + if( !strncmp( arg, "--int-reg=", 10 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_integer_register(arg + 10, &options->output) ) + { + fprintf( stderr, "%s: bad --int-reg operand: %s\n", + options->common.progname, arg + 10 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-reg=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_real_register(arg + 11, &options->output) ) + { + fprintf( stderr, "%s: bad --real-reg operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-data=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 11, &options->output, 0, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --int-data operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-data=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 1, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --real-data operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-data=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 11, &options->output, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --raw-data operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-control=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 14, &options->output, 0, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --int-control operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-control=", 15 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 15, &options->output, 1, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --real-control operand: %s\n", + options->common.progname, arg + 15 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-control=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 14, &options->output, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --raw-control operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-heap=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 11, &options->output, 0, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --int-heap operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-heap=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 1, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --real-heap operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-heap=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 11, &options->output, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --raw-heap operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-input=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 0, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --int-input operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-input=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 1, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --real-input operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-input=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 12, &options->output, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --raw-input operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-output=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 0, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --int-output operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-output=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 14, &options->output, 1, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --real-output operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-output=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 13, &options->output, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --raw-output operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-trace=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 0, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --int-trace operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-trace=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 1, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --real-trace operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-trace=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 12, &options->output, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --raw-trace operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--before-run=", 13 ) ) + { + if( parse_stage_option(arg + 13, &options->before_run) ) + { + fprintf( stderr, "%s: bad --before-run action: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--after-init=", 13 ) ) + { + if( parse_stage_option(arg + 13, &options->after_init) ) + { + fprintf( stderr, "%s: bad --after-init action: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--on-step=", 10 ) ) + { + if( parse_stage_option(arg + 10, &options->on_step) ) + { + fprintf( stderr, "%s: bad --on-step action: %s\n", + options->common.progname, arg + 10 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--after-run=", 12 ) ) + { + if( parse_stage_option(arg + 12, &options->after_run) ) + { + fprintf( stderr, "%s: bad --after-run action: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--library-path=", 15 ) ) + { + if( !arg[15] || + mcpu_tool_add_library_path(&options->common, arg + 15) != 0 ) + { + fprintf( stderr, "%s: bad --library-path: %s\n", + options->common.progname, arg + 15 ); + return( -1 ); + } + } + else if( !strcmp( arg, "--library-path" ) ) + { + if( ++i >= argc || argv[i][0] == 0 || + mcpu_tool_add_library_path(&options->common, argv[i]) != 0 ) + { + fprintf( stderr, "%s: argument missing or invalid after --library-path\n", + options->common.progname ); + return( -1 ); + } + } + else if( !strncmp( arg, "--ldpreload=", 12 ) ) + { + if( options->ldpreload != NULL || !arg[12] ) + { + fprintf( stderr, + "%s: --ldpreload must be supplied once with a non-empty ':' list\n", + options->common.progname ); + return( -1 ); + } + options->ldpreload = arg + 12; + } + else if( !strncmp( arg, "--steps=", 8 ) ) + { + if( parse_u64(arg + 8, &options->steps) || !options->steps ) + { + fprintf( stderr, "%s: --steps must be positive\n", + options->common.progname ); + return( -1 ); + } + options->steps_set = 1; + } + else if( !strcmp( arg, "--init-only" ) ) + options->init_only = 1; + else if( !strncmp( arg, "--digits=", 9 ) ) + { + char *end; + unsigned long n; + + errno = 0; + n = strtoul( arg + 9, &end, 10 ); + if( errno || end == arg + 9 || *end || !n || n > 1000000UL ) + { + fprintf( stderr, "%s: bad --digits value\n", + options->common.progname ); + return( -1 ); + } + options->digits = (unsigned)n; + } + else if( !strncmp( arg, "--stack-size", 12 ) || + !strncmp( arg, "--heap-size", 11 ) || + !strncmp( arg, "--heap-limit", 12 ) ) + { + fprintf( stderr, + "%s: STACK/HEAP sizing is part of the MELF executable ABI; " + "runtime size options were removed\n", + options->common.progname ); + return( -1 ); + } + else if( arg[0] == '-' ) + { + fprintf( stderr, "%s: unknown option: %s\n", + options->common.progname, arg ); + mcpu_run_usage( options ); + return( -1 ); + } + else + { + options->exe = arg; + exearg = i; + break; + } + } + + if( options->common.help || + mcpu_tool_has_information_action(&options->common) ) + return( 0 ); + + if( options->exe == NULL ) + { + mcpu_run_usage( options ); + return( -1 ); + } + + options->program_argc = argc - exearg; + options->program_argv = argv + exearg; + return( 0 ); + +} /* End of mcpu_run_handle_options() */ diff --git a/utils/mcpu-run-options.h b/utils/mcpu-run-options.h new file mode 100644 index 0000000..06f9412 --- /dev/null +++ b/utils/mcpu-run-options.h @@ -0,0 +1,105 @@ +#ifndef __MCPU_RUN_OPTIONS_H__ +#define __MCPU_RUN_OPTIONS_H__ 1 + +#include <stddef.h> + +#include <libmcpu.h> + +#include "mcpu-tool-options.h" + +typedef enum __print_kind +{ + PRINT_NONE = 0, + PRINT_INTEGER_REGISTER, + PRINT_REAL_REGISTER, + PRINT_INTEGER_MEMORY, + PRINT_REAL_MEMORY, + PRINT_RAW_MEMORY +} print_kind; + +typedef enum __memory_kind +{ + MEMORY_NONE = 0, + MEMORY_DATA, + MEMORY_CONTROL, + MEMORY_HEAP, + MEMORY_INPUT, + MEMORY_OUTPUT, + MEMORY_TRACE +} memory_kind; + +typedef struct __print_spec +{ + print_kind kind; + memory_kind memory; + unsigned int bits; + __mpu_uint64_t address; + __mpu_uint64_t count; +} print_spec; + +typedef enum __action_kind +{ + ACTION_NONE = 0, + ACTION_PRINT_INTEGER_REGISTER, + ACTION_PRINT_REAL_REGISTER, + ACTION_SET_INTEGER_REGISTER, + ACTION_SET_REAL_REGISTER, + ACTION_PRINT_INTEGER_MEMORY, + ACTION_PRINT_REAL_MEMORY, + ACTION_SET_INTEGER_MEMORY, + ACTION_SET_REAL_MEMORY, + ACTION_PRINT_RAW_MEMORY +} action_kind; + +typedef struct __action_item +{ + unsigned int bits; + __mpu_uint64_t address; + char *value; +} action_item; + +typedef struct __stage_action +{ + action_kind kind; + memory_kind memory; + action_item *item; + size_t count; + print_spec raw; +} stage_action; + +typedef struct __stage_action_list +{ + stage_action *action; + size_t count; + size_t capacity; +} stage_action_list; + +typedef struct mcpu_run_options mcpu_run_options; +struct mcpu_run_options +{ + mcpu_tool_options common; + print_spec output; + stage_action_list before_run; + stage_action_list after_init; + stage_action_list on_step; + stage_action_list after_run; + unsigned int digits; + __mpu_uint64_t steps; + int steps_set; + int init_only; + const char *exe; + const char *env_ldpreload; + const char *ldpreload; + int program_argc; + char **program_argv; +}; + +void mcpu_run_options_init( mcpu_run_options *options, const char *progname ); +void mcpu_run_options_free( mcpu_run_options *options ); +int mcpu_run_handle_options( mcpu_run_options *options, + int argc, char **argv ); +void mcpu_run_usage( const mcpu_run_options *options ); +int mcpu_run_resolve_preload( const mcpu_run_options *options, + char **resolved, char *err, size_t errsz ); + +#endif /* __MCPU_RUN_OPTIONS_H__ */ diff --git a/utils/mcpu-run.c b/utils/mcpu-run.c new file mode 100644 index 0000000..d79e034 --- /dev/null +++ b/utils/mcpu-run.c @@ -0,0 +1,870 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-exec.h" +#include "mcpu-dependency.h" + +#include "mcpu-run-options.h" + +static const char * +memory_name( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( "DATA" ); + case MEMORY_CONTROL: return( "CONTROL" ); + case MEMORY_HEAP: return( "HEAP" ); + case MEMORY_INPUT: return( "INPUT" ); + case MEMORY_OUTPUT: return( "OUTPUT" ); + case MEMORY_TRACE: return( "TRACE" ); + default: return( "UNKNOWN" ); + } + +} /* End of memory_name() */ + + +static __mpu_UNIT_t * +memory_ptr( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( __cpu_data_ptr() ); + case MEMORY_CONTROL: return( __cpu_control_ptr() ); + case MEMORY_HEAP: return( __cpu_heap_ptr() ); + case MEMORY_INPUT: return( __cpu_input_ptr() ); + case MEMORY_OUTPUT: return( __cpu_output_ptr() ); + case MEMORY_TRACE: return( __cpu_trace_ptr() ); + default: return( (__mpu_UNIT_t *)0 ); + } + +} /* End of memory_ptr() */ + + +static __mpu_uint64_t +memory_size( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( __cpu_data_size() ); + case MEMORY_CONTROL: return( __cpu_control_size() ); + case MEMORY_HEAP: return( __cpu_heap_size() ); + case MEMORY_INPUT: return( __cpu_input_size() ); + case MEMORY_OUTPUT: return( __cpu_output_size() ); + case MEMORY_TRACE: return( __cpu_trace_size() ); + default: return( 0 ); + } + +} /* End of memory_size() */ + + + +static int +make_integer_format( __mpu_char16_t *format, size_t size, + unsigned int bits, int newline ) +{ + char ascii[64]; + int len, i; + + len = snprintf( ascii, sizeof( ascii ), newline ? "%%Z%ud\n" : "%%Z%ud", bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) || (size_t)len + 1U > size ) + return( -1 ); + + for( i = 0; i <= len; ++i ) + format[i] = (__mpu_char16_t)(unsigned char)ascii[i]; + + return( 0 ); + +} /* End of make_integer_format() */ + + +static __mpu_UNIT_t * +memory_object_ptr( const print_spec *spec, __mpu_uint64_t bytes, + const char *object_name ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t size; + + p = memory_ptr( spec->memory ); + size = memory_size( spec->memory ); + + if( !p || spec->address > size || bytes > size - spec->address ) + { + fprintf( stderr, "mcpu-run: %s %s object is outside %s memory\n", + object_name, memory_name( spec->memory ), + memory_name( spec->memory ) ); + return( (__mpu_UNIT_t *)0 ); + } + + return( p + (size_t)spec->address ); + +} /* End of memory_object_ptr() */ + + +static int +print_integer_value( const print_spec *spec, int newline ) +{ + __mpu_char16_t format[64]; + __mpu_UNIT_t *value; + __mpu_uint64_t bytes; + + if( make_integer_format( format, sizeof( format ) / sizeof( format[0] ), + spec->bits, newline ) ) + return( -1 ); + + bytes = spec->bits / 8U; + if( spec->kind == PRINT_INTEGER_REGISTER ) + value = __cpu_reg_ptr( spec->bits, (__mpu_uint32_t)spec->address ); + else if( spec->kind == PRINT_INTEGER_MEMORY ) + value = memory_object_ptr( spec, bytes, "integer" ); + else + return( -1 ); + + if( !value ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + + if( mpu_dprintf( STDOUT_FILENO, format, (mpu_int *)value ) < 0 ) + { + fprintf( stderr, "mcpu-run: LibMPUIO integer output failed\n" ); + return( -1 ); + } + + return( 0 ); + +} /* End of print_integer_value() */ + + +static int +print_integer( const print_spec *spec ) +{ + return( print_integer_value( spec, 1 ) ); + +} /* End of print_integer() */ + + +static int +make_real_format( __mpu_char16_t *format, size_t size, + unsigned int bits, unsigned int digits, int newline ) +{ + char ascii[96]; + int len, i; + + len = snprintf( ascii, sizeof( ascii ), newline ? "%%.%uZ%uR\n" : "%%.%uZ%uR", + digits, bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) || (size_t)len + 1U > size ) + return( -1 ); + + for( i = 0; i <= len; ++i ) + format[i] = (__mpu_char16_t)(unsigned char)ascii[i]; + + return( 0 ); + +} /* End of make_real_format() */ + + +static int +print_real_value( const print_spec *spec, unsigned int digits, int newline ) +{ + __mpu_char16_t format[96]; + __mpu_UNIT_t *value; + __mpu_uint64_t bytes; + + if( make_real_format( format, sizeof( format ) / sizeof( format[0] ), + spec->bits, digits, newline ) ) + return( -1 ); + + bytes = spec->bits / 8U; + if( spec->kind == PRINT_REAL_REGISTER ) + value = __cpu_reg_ptr( spec->bits, (__mpu_uint32_t)spec->address ); + else if( spec->kind == PRINT_REAL_MEMORY ) + value = memory_object_ptr( spec, bytes, "real" ); + else + return( -1 ); + + if( !value ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + + if( mpu_dprintf( STDOUT_FILENO, format, (mpu_real *)value ) < 0 ) + { + fprintf( stderr, "mcpu-run: LibMPUIO real output failed\n" ); + return( -1 ); + } + + return( 0 ); + +} /* End of print_real_value() */ + + +static int +print_real( const print_spec *spec, unsigned int digits ) +{ + return( print_real_value( spec, digits, 1 ) ); + +} /* End of print_real() */ + +static int +raw_memory_range( const print_spec *spec, const cpu_exec_loaded *loaded, + __mpu_UNIT_t **ptr, __mpu_uint64_t *count ) +{ + __mpu_uint64_t begin, end, size; + __mpu_UNIT_t *p; + + if( spec->memory == MEMORY_DATA ) + { + if( !loaded->has_data_section ) + { + fprintf( stderr, "mcpu-run: executable has no .data section\n" ); + return( -1 ); + } + + begin = loaded->data_section_begin; + if( loaded->data_section_size > UINT64_MAX - begin ) return( -1 ); + end = begin + loaded->data_section_size; + + if( spec->address < begin || spec->address >= end ) + { + fprintf( stderr, "mcpu-run: raw DATA address is outside .data\n" ); + return( -1 ); + } + + p = __cpu_data_ptr(); + } + else + { + begin = 0; + size = memory_size( spec->memory ); + p = memory_ptr( spec->memory ); + end = size; + + if( !p || spec->address >= end ) + { + fprintf( stderr, "mcpu-run: raw %s address is outside %s memory\n", + memory_name( spec->memory ), memory_name( spec->memory ) ); + return( -1 ); + } + } + + *count = spec->count; + if( *count > end - spec->address ) *count = end - spec->address; + + *ptr = p + (size_t)spec->address; + + return( 0 ); + +} /* End of raw_memory_range() */ + + +static int +print_raw_memory( const print_spec *spec, const cpu_exec_loaded *loaded ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t count, off; + + if( spec->kind != PRINT_RAW_MEMORY ) return( -1 ); + if( raw_memory_range( spec, loaded, &p, &count ) ) return( -1 ); + + printf( "%s memory dump: (%llu bytes):\n", + memory_name( spec->memory ), (unsigned long long)count ); + + for( off = 0; off < count; off += 16 ) + { + unsigned int j; + unsigned int row = (unsigned int)((count - off) > 16 ? 16 : (count - off)); + + printf( " %04llx : ", (unsigned long long)off ); + for( j = 0; j < 16; ++j ) + { + if( j < row ) + printf( "%02x ", (unsigned int)p[(size_t)off + j] ); + else + printf( " " ); + } + printf( " | " ); + for( j = 0; j < row; ++j ) + { + unsigned int c = (unsigned int)p[(size_t)off + j]; + putchar( c >= 0x20U && c <= 0x7eU ? (int)c : '.' ); + } + putchar( '\n' ); + } + + return( ferror( stdout ) ? -1 : 0 ); + +} /* End of print_raw_memory() */ + + +static int +write_stdout( const char *s, size_t n ) +{ + while( n ) + { + ssize_t rc = write( STDOUT_FILENO, s, n ); + + if( rc < 0 ) + { + if( errno == EINTR ) continue; + return( -1 ); + } + if( !rc ) return( -1 ); + + s += (size_t)rc; + n -= (size_t)rc; + } + + return( 0 ); + +} /* End of write_stdout() */ + + +static __mpu_char16_t * +ascii_to_ucs2( const char *s ) +{ + __mpu_char16_t *p; + size_t i, n; + + if( !s ) return( (__mpu_char16_t *)0 ); + + n = strlen( s ); + if( n > (SIZE_MAX / sizeof( *p )) - 1U ) return( (__mpu_char16_t *)0 ); + + p = (__mpu_char16_t *)malloc( (n + 1U) * sizeof( *p ) ); + if( !p ) return( (__mpu_char16_t *)0 ); + + for( i = 0; i < n; ++i ) + { + unsigned char c = (unsigned char)s[i]; + + if( c >= 0x80U ) + { + free( p ); + return( (__mpu_char16_t *)0 ); + } + p[i] = (__mpu_char16_t)c; + } + p[n] = 0; + + return( p ); + +} /* End of ascii_to_ucs2() */ + + +static int +scan_action_value( const char *value, unsigned int bits, + int real_object, __mpu_UNIT_t *dst ) +{ + __mpu_char16_t *text = (__mpu_char16_t *)0; + __mpu_char16_t *format = (__mpu_char16_t *)0; + __mpu_uint32_t psw; + char ascii[64]; + size_t n; + int len, consumed = -1, rc; + + n = strlen( value ); + len = snprintf( ascii, sizeof( ascii ), real_object ? "%%Z%uR%%n" : "%%Z%ui%%n", + bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) ) return( -1 ); + + text = ascii_to_ucs2( value ); + format = ascii_to_ucs2( ascii ); + if( !text || !format ) goto fail; + + psw = __cpu_get_psw(); + if( real_object ) + rc = mpu_sscanf( text, format, (mpu_real *)dst, &consumed ); + else + rc = mpu_sscanf( text, format, (mpu_int *)dst, &consumed ); + __cpu_set_psw( psw ); + + free( format ); + free( text ); + + if( rc != 1 || consumed < 0 || (size_t)consumed != n ) return( -1 ); + + return( 0 ); + +fail: + free( format ); + free( text ); + return( -1 ); + +} /* End of scan_action_value() */ + + +static void +make_action_print_spec( const stage_action *action, const action_item *item, + print_spec *spec ) +{ + memset( spec, 0, sizeof( *spec ) ); + spec->memory = action->memory; + spec->bits = item->bits; + spec->address = item->address; + + if( action->kind == ACTION_PRINT_INTEGER_REGISTER || + action->kind == ACTION_SET_INTEGER_REGISTER ) + spec->kind = PRINT_INTEGER_REGISTER; + else if( action->kind == ACTION_PRINT_REAL_REGISTER || + action->kind == ACTION_SET_REAL_REGISTER ) + spec->kind = PRINT_REAL_REGISTER; + else if( action->kind == ACTION_PRINT_INTEGER_MEMORY || + action->kind == ACTION_SET_INTEGER_MEMORY ) + spec->kind = PRINT_INTEGER_MEMORY; + else if( action->kind == ACTION_PRINT_REAL_MEMORY || + action->kind == ACTION_SET_REAL_MEMORY ) + spec->kind = PRINT_REAL_MEMORY; + +} /* End of make_action_print_spec() */ + + +static __mpu_UNIT_t * +action_object_ptr( const stage_action *action, const action_item *item, + const char *object_name ) +{ + print_spec spec; + + make_action_print_spec( action, item, &spec ); + if( spec.kind == PRINT_INTEGER_REGISTER || spec.kind == PRINT_REAL_REGISTER ) + return( __cpu_reg_ptr( spec.bits, (__mpu_uint32_t)spec.address ) ); + + return( memory_object_ptr( &spec, spec.bits / 8U, object_name ) ); + +} /* End of action_object_ptr() */ + + +static int +execute_print_action( const stage_action *action, unsigned int digits ) +{ + print_spec spec; + size_t i; + + /* Validate the whole action before producing any output. */ + for( i = 0; i < action->count; ++i ) + { + if( !action_object_ptr( action, &action->item[i], + (action->kind == ACTION_PRINT_REAL_REGISTER || + action->kind == ACTION_PRINT_REAL_MEMORY) ? + "real" : "integer" ) ) + return( -1 ); + } + + for( i = 0; i < action->count; ++i ) + { + make_action_print_spec( action, &action->item[i], &spec ); + + if( i && write_stdout( " ", 1U ) ) return( -1 ); + + if( spec.kind == PRINT_INTEGER_REGISTER || spec.kind == PRINT_INTEGER_MEMORY ) + { + if( print_integer_value( &spec, 0 ) ) return( -1 ); + } + else + { + if( print_real_value( &spec, digits, 0 ) ) return( -1 ); + } + } + + if( write_stdout( "\n", 1U ) ) return( -1 ); + + return( 0 ); + +} /* End of execute_print_action() */ + + +typedef struct __set_value +{ + __mpu_UNIT_t *dst; + __mpu_UNIT_t *value; + size_t bytes; +} set_value; + + +static int +execute_set_action( const stage_action *action ) +{ + set_value *value; + size_t i, j; + int real_object; + + value = (set_value *)calloc( action->count, sizeof( *value ) ); + if( !value ) return( -1 ); + + real_object = (action->kind == ACTION_SET_REAL_REGISTER || + action->kind == ACTION_SET_REAL_MEMORY); + + /* Parse and validate every assignment before changing guest state. */ + for( i = 0; i < action->count; ++i ) + { + value[i].bytes = action->item[i].bits / 8U; + value[i].dst = action_object_ptr( action, &action->item[i], + real_object ? "real" : "integer" ); + if( !value[i].dst ) goto fail; + + value[i].value = (__mpu_UNIT_t *)calloc( value[i].bytes, 1U ); + if( !value[i].value ) goto fail; + + if( scan_action_value( action->item[i].value, action->item[i].bits, + real_object, value[i].value ) ) + { + fprintf( stderr, "mcpu-run: cannot parse stage value '%s' as %s%u\n", + action->item[i].value, real_object ? "real" : "int", + action->item[i].bits ); + goto fail; + } + } + + for( i = 0; i < action->count; ++i ) + memcpy( value[i].dst, value[i].value, value[i].bytes ); + + for( i = 0; i < action->count; ++i ) free( value[i].value ); + free( value ); + + return( 0 ); + +fail: + for( j = 0; j < action->count; ++j ) free( value[j].value ); + free( value ); + return( -1 ); + +} /* End of execute_set_action() */ + + +static int +execute_stage_action( const stage_action *action, unsigned int digits, + const cpu_exec_loaded *loaded ) +{ + switch( action->kind ) + { + case ACTION_PRINT_INTEGER_REGISTER: + case ACTION_PRINT_REAL_REGISTER: + case ACTION_PRINT_INTEGER_MEMORY: + case ACTION_PRINT_REAL_MEMORY: + return( execute_print_action( action, digits ) ); + + case ACTION_SET_INTEGER_REGISTER: + case ACTION_SET_REAL_REGISTER: + case ACTION_SET_INTEGER_MEMORY: + case ACTION_SET_REAL_MEMORY: + return( execute_set_action( action ) ); + + case ACTION_PRINT_RAW_MEMORY: + if( print_raw_memory( &action->raw, loaded ) ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + return( 0 ); + + default: + return( -1 ); + } + +} /* End of execute_stage_action() */ + + +static int +execute_stage_action_list( const stage_action_list *list, unsigned int digits, + const cpu_exec_loaded *loaded ) +{ + size_t i; + + for( i = 0; i < list->count; ++i ) + if( execute_stage_action( &list->action[i], digits, loaded ) ) + return( -1 ); + + return( 0 ); + +} /* End of execute_stage_action_list() */ + + +static int +execution_fault( int rc ) +{ + fprintf( stderr, "mcpu-run: execution fault %d at PC=0x%016llx, PSW=0x%08x\n", + rc, (unsigned long long)__cpu_pc, (unsigned int)__cpu_get_psw()); + return( 1 ); +} + + +static int +mcpu_run_execute( const mcpu_run_options *options ) +{ + unsigned long long completed = 0; + cpu_exec_loaded loaded; + char err[512]; + char *resolved_preload = NULL; + char *resolved_modules = NULL; + size_t preload_count = 0; + int rc; + int host_status = 0; + + if( options == NULL ) + return( 1 ); + + if( mcpu_run_resolve_preload(options, &resolved_preload, + err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: preload resolution error: %s\n", + options->common.progname, err ); + return( 1 ); + } + + if( mcpu_dependency_build_load_list(&options->common, options->exe, + resolved_preload, &resolved_modules, + &preload_count, err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: dependency resolution error: %s\n", + options->common.progname, err ); + free( resolved_preload ); + return( 1 ); + } + + __cpu_init(); + + /* Guest argv begins with the executable spelling supplied to mcpu-run. */ + if( cpu_exec_load_resolved(options->exe, resolved_modules, + preload_count, + options->program_argc, options->program_argv, + &loaded, err, sizeof(err)) ) + { + fprintf( stderr, "%s: load error: %s\n", + options->common.progname, err ); + __cpu_free_context(); + free( resolved_modules ); + free( resolved_preload ); + return( 1 ); + } + + if( execute_stage_action_list(&options->before_run, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --before-run action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( loaded.has_init ) + { + __cpu_pc = loaded.init_entry; + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc == CPU_EXEC_EXIT ) + goto graceful_exit; + if( __cpu_get_stop_reason() == CPU_STOP_HALT ) + goto stopped; + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: INIT did not terminate with END\n", + options->common.progname ); + host_status = 1; + goto done; + } + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, "%s: INIT did not restore top-level STACK state\n", + options->common.progname ); + host_status = 1; + goto done; + } + __cpu_pc = loaded.life_entry; + } + + if( execute_stage_action_list(&options->after_init, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --after-init action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( options->init_only ) + goto stopped; + + for( ; ; ) + { + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc == CPU_EXEC_EXIT ) + goto graceful_exit; + if( __cpu_get_stop_reason() == CPU_STOP_HALT ) + goto stopped; + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: LIFE phase stopped unexpectedly (%d)\n", + options->common.progname, rc ); + host_status = 1; + goto done; + } + ++completed; + if( execute_stage_action_list(&options->on_step, + options->digits, &loaded) ) + { + fprintf( stderr, + "%s: --on-step action failed after LIFE step %llu\n", + options->common.progname, completed ); + host_status = 1; + goto done; + } + if( !options->steps_set || completed >= options->steps ) + goto stopped; + /* Resume from the PC already advanced past END. */ + } + +graceful_exit: + host_status = (int)__cpu_get_exit_status(); + if( loaded.has_fini ) + { + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, + "%s: LIFE did not restore top-level STACK state before FINI\n", + options->common.progname ); + host_status = 1; + goto done; + } + __cpu_pc = loaded.fini_entry; + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: FINI must terminate with END\n", + options->common.progname ); + host_status = 1; + goto done; + } + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, "%s: FINI did not restore top-level STACK state\n", + options->common.progname ); + host_status = 1; + goto done; + } + } + +stopped: + if( execute_stage_action_list(&options->after_run, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --after-run action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( options->output.kind == PRINT_INTEGER_REGISTER || + options->output.kind == PRINT_INTEGER_MEMORY ) + { + if( print_integer(&options->output) ) + { + host_status = 1; + goto done; + } + } + else if( options->output.kind == PRINT_REAL_REGISTER || + options->output.kind == PRINT_REAL_MEMORY ) + { + if( print_real(&options->output, options->digits) ) + { + host_status = 1; + goto done; + } + } + else if( options->output.kind == PRINT_RAW_MEMORY ) + { + if( print_raw_memory(&options->output, &loaded) ) + { + host_status = 1; + goto done; + } + } + +done: + __cpu_free_context(); + free( resolved_modules ); + free( resolved_preload ); + if( host_status < 0 || host_status > 255 ) + return( 1 ); + return( host_status ); + +} /* End of mcpu_run_execute() */ + + +int +main( int argc, char **argv ) +{ + mcpu_run_options options; + int rc = 2; + + mcpu_run_options_init( &options, argv[0] ); + + if( mcpu_run_handle_options(&options, argc, argv) != 0 ) + goto done; + + if( options.common.help ) + { + mcpu_run_usage( &options ); + rc = 0; + goto done; + } + + if( options.common.version_action != MCPU_VERSION_NONE ) + { + if( mcpu_version_print(options.common.progname, + options.common.version_action) != 0 ) + { + rc = 1; + goto done; + } + rc = 0; + goto done; + } + + if( mcpu_tool_options_prepare(&options.common, argv[0]) != 0 ) + { + rc = 1; + goto done; + } + + if( mcpu_tool_has_information_action(&options.common) ) + { + if( options.common.dump_config && + mcpu_tool_dump_config(&options.common, stdout) != 0 ) + { + rc = 1; + goto done; + } + if( options.common.print_search_dirs && + mcpu_tool_print_search_dirs(&options.common, stdout) != 0 ) + { + rc = 1; + goto done; + } + rc = 0; + goto done; + } + + rc = mcpu_run_execute( &options ); + +done: + mcpu_run_options_free( &options ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-runtime.c b/utils/mcpu-runtime.c new file mode 100644 index 0000000..7ed6835 --- /dev/null +++ b/utils/mcpu-runtime.c @@ -0,0 +1,366 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mcpu-runtime.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + + +static char * +path_dirname( const char *path ) +{ + const char *slash; + size_t length; + char *result; + + if( path == NULL || *path == 0 ) + { + errno = EINVAL; + return( NULL ); + } + + slash = strrchr( path, '/' ); + if( slash == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + if( slash == path ) + length = 1; + else + length = (size_t)(slash - path); + + result = (char *)malloc( length + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, path, length ); + result[length] = 0; + return( result ); +} + + +static char * +path_join( const char *directory, const char *name ) +{ + size_t directory_length; + size_t name_length; + int need_slash; + char *result; + + if( directory == NULL || name == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + directory_length = strlen( directory ); + name_length = strlen( name ); + need_slash = directory_length != 0 && directory[directory_length - 1] != '/'; + + if( directory_length > SIZE_MAX - name_length - (size_t)need_slash - 1 ) + { + errno = EOVERFLOW; + return( NULL ); + } + + result = (char *)malloc( directory_length + name_length + + (size_t)need_slash + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, directory, directory_length ); + if( need_slash ) + result[directory_length++] = '/'; + memcpy( result + directory_length, name, name_length ); + result[directory_length + name_length] = 0; + return( result ); +} + + +static char * +path_absolute( const char *path ) +{ + char cwd[PATH_MAX]; + char *result; + + if( path == NULL || *path == 0 ) + { + errno = EINVAL; + return( NULL ); + } + + if( path[0] == '/' ) + return( strdup(path) ); + + if( getcwd(cwd, sizeof(cwd)) == NULL ) + return( NULL ); + + result = path_join( cwd, path ); + return( result ); +} + + +static int +resolve_proc_self_exe( char path[PATH_MAX] ) +{ + ssize_t length; + + length = readlink( "/proc/self/exe", path, PATH_MAX - 1 ); + if( length < 0 ) + return( -1 ); + + if( length >= PATH_MAX - 1 ) + { + errno = ENAMETOOLONG; + return( -1 ); + } + + path[length] = 0; + return( 0 ); +} + + +static int +resolve_candidate( const char *candidate, char path[PATH_MAX] ) +{ + if( candidate == NULL || *candidate == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( realpath(candidate, path) == NULL ) + return( -1 ); + + if( access(path, X_OK) != 0 ) + return( -1 ); + + return( 0 ); +} + + +static int +resolve_argv0( const char *argv0, char path[PATH_MAX] ) +{ + const char *search; + const char *entry; + + if( argv0 == NULL || *argv0 == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( strchr(argv0, '/') != NULL ) + return( resolve_candidate(argv0, path) ); + + search = getenv( "PATH" ); + if( search == NULL ) + { + errno = ENOENT; + return( -1 ); + } + + entry = search; + for( ;; ) + { + const char *colon = strchr( entry, ':' ); + size_t length = colon ? (size_t)(colon - entry) : strlen(entry); + const char *directory = entry; + char directory_buffer[PATH_MAX]; + char *candidate; + int rc; + + if( length == 0 ) + directory = "."; + else + { + if( length >= sizeof(directory_buffer) ) + { + errno = ENAMETOOLONG; + return( -1 ); + } + memcpy( directory_buffer, entry, length ); + directory_buffer[length] = 0; + directory = directory_buffer; + } + + candidate = path_join( directory, argv0 ); + if( candidate == NULL ) + return( -1 ); + rc = resolve_candidate( candidate, path ); + free( candidate ); + if( rc == 0 ) + return( 0 ); + + if( colon == NULL ) + break; + entry = colon + 1; + } + + errno = ENOENT; + return( -1 ); +} + + +void +mcpu_runtime_paths_init( mcpu_runtime_paths *paths ) +{ + if( paths == NULL ) + return; + + paths->executable_path = NULL; + paths->executable_dir = NULL; + paths->installation_root = NULL; + paths->config_file = NULL; + paths->system_library_path = NULL; +} + + +void +mcpu_runtime_paths_free( mcpu_runtime_paths *paths ) +{ + if( paths == NULL ) + return; + + free( paths->executable_path ); + free( paths->executable_dir ); + free( paths->installation_root ); + free( paths->config_file ); + free( paths->system_library_path ); + mcpu_runtime_paths_init( paths ); +} + + +int +mcpu_runtime_paths_discover( mcpu_runtime_paths *paths, const char *argv0 ) +{ + char resolved[PATH_MAX]; + char *executable_path = NULL; + char *executable_dir = NULL; + char *installation_root = NULL; + char *config_file = NULL; + char *system_library_path = NULL; + + if( paths == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( resolve_proc_self_exe(resolved) != 0 ) + { + if( resolve_argv0(argv0, resolved) != 0 ) + return( -1 ); + } + + executable_path = strdup( resolved ); + if( executable_path == NULL ) + goto fail; + + executable_dir = path_dirname( executable_path ); + if( executable_dir == NULL ) + goto fail; + + installation_root = path_dirname( executable_dir ); + if( installation_root == NULL ) + goto fail; + + config_file = path_join( installation_root, "etc/mcpu-ld.conf" ); + if( config_file == NULL ) + goto fail; + + system_library_path = path_join( installation_root, "lib" ); + if( system_library_path == NULL ) + goto fail; + + mcpu_runtime_paths_free( paths ); + paths->executable_path = executable_path; + paths->executable_dir = executable_dir; + paths->installation_root = installation_root; + paths->config_file = config_file; + paths->system_library_path = system_library_path; + return( 0 ); + +fail: + free( executable_path ); + free( executable_dir ); + free( installation_root ); + free( config_file ); + free( system_library_path ); + return( -1 ); +} + + +int +mcpu_runtime_paths_set_root( mcpu_runtime_paths *paths, const char *root ) +{ + char *installation_root; + char *config_file; + char *system_library_path; + + if( paths == NULL || root == NULL || *root == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + installation_root = path_absolute( root ); + if( installation_root == NULL ) + return( -1 ); + + config_file = path_join( installation_root, "etc/mcpu-ld.conf" ); + if( config_file == NULL ) + goto fail; + + system_library_path = path_join( installation_root, "lib" ); + if( system_library_path == NULL ) + goto fail; + + free( paths->installation_root ); + free( paths->config_file ); + free( paths->system_library_path ); + paths->installation_root = installation_root; + paths->config_file = config_file; + paths->system_library_path = system_library_path; + return( 0 ); + +fail: + free( installation_root ); + free( config_file ); + return( -1 ); +} + + +int +mcpu_runtime_paths_set_system_library( mcpu_runtime_paths *paths, + const char *path ) +{ + char *copy; + + if( paths == NULL || path == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + copy = strdup( path ); + if( copy == NULL ) + return( -1 ); + + free( paths->system_library_path ); + paths->system_library_path = copy; + return( 0 ); +} diff --git a/utils/mcpu-runtime.h b/utils/mcpu-runtime.h new file mode 100644 index 0000000..d68f7a6 --- /dev/null +++ b/utils/mcpu-runtime.h @@ -0,0 +1,23 @@ +#ifndef __MCPU_RUNTIME_H__ +#define __MCPU_RUNTIME_H__ 1 + +typedef struct mcpu_runtime_paths mcpu_runtime_paths; +struct mcpu_runtime_paths +{ + char *executable_path; + char *executable_dir; + char *installation_root; + char *config_file; + char *system_library_path; +}; + +void mcpu_runtime_paths_init( mcpu_runtime_paths *paths ); +void mcpu_runtime_paths_free( mcpu_runtime_paths *paths ); +int mcpu_runtime_paths_discover( mcpu_runtime_paths *paths, + const char *argv0 ); +int mcpu_runtime_paths_set_root( mcpu_runtime_paths *paths, + const char *root ); +int mcpu_runtime_paths_set_system_library( mcpu_runtime_paths *paths, + const char *path ); + +#endif /* __MCPU_RUNTIME_H__ */ diff --git a/utils/mcpu-strip.c b/utils/mcpu-strip.c new file mode 100644 index 0000000..14f4b08 --- /dev/null +++ b/utils/mcpu-strip.c @@ -0,0 +1,332 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" +#include "mcpu-version.h" + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file\n\n" + "Strip non-runtime symbol metadata from LibMCPU MELF files and archives.\n\n" + "The current safe policy removes ordinary SHT_SYMTAB/SHT_STRTAB metadata\n" + "from ET_EXEC/ET_DYN only when no retained section depends on it. ET_REL\n" + "symbol tables, dynamic-linking tables, relocations and MCPU metadata are\n" + "always preserved.\n\n" + "Options:\n" + " -o, --output=FILE - write result to FILE instead of replacing input\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +make_temp_name( const char *path, char **name, char *err, size_t errsz ) +{ + size_t n = strlen( path ); + char *p; + + if( n > SIZE_MAX - 32 ) + { + snprintf( err, errsz, "output path is too long" ); + return( -1 ); + } + p = (char *)malloc( n + 32 ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + snprintf( p, n + 32, "%s.mcpu-strip-XXXXXX", path ); + *name = p; + return( 0 ); + +} /* End of make_temp_name() */ + + +static int +commit_bytes( const char *input, const char *output, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + struct stat st; + char *tmp = NULL; + int fd = -1; + int rc = -1; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( make_temp_name( output, &tmp, err, errsz ) ) + return( -1 ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( close( fd ) ) + { + fd = -1; + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + fd = -1; + + if( cpu_melf_writeall( tmp, data, size, err, errsz ) ) + goto out; + if( chmod( tmp, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( rename( tmp, output ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + goto out; + } + rc = 0; + +out: + if( fd >= 0 ) + close( fd ); + if( rc && tmp ) + unlink( tmp ); + free( tmp ); + return( rc ); + +} /* End of commit_bytes() */ + + +static int +commit_archive( const char *input, const char *output, + const mcpu_archive *archive, int index, + char *err, size_t errsz ) +{ + struct stat st; + char *tmp = NULL; + int fd = -1; + int rc = -1; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( make_temp_name( output, &tmp, err, errsz ) ) + return( -1 ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( close( fd ) ) + { + fd = -1; + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + fd = -1; + + if( mcpu_archive_write( tmp, archive, index, err, errsz ) ) + goto out; + if( chmod( tmp, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( rename( tmp, output ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + goto out; + } + rc = 0; + +out: + if( fd >= 0 ) + close( fd ); + if( rc && tmp ) + unlink( tmp ); + free( tmp ); + return( rc ); + +} /* End of commit_archive() */ + + +static int +strip_archive( const char *input, const char *output, + const unsigned char *original, size_t original_size, + char *err, size_t errsz ) +{ + mcpu_archive archive; + size_t i; + int any = 0; + int rc = -1; + + mcpu_archive_init( &archive ); + if( mcpu_archive_read( input, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + int changed = 0; + + if( mcpu_object_strip_data( archive.members[i].data, + archive.members[i].size, + &p, &n, &changed, err, errsz ) ) + goto out; + if( changed ) + { + free( archive.members[i].data ); + archive.members[i].data = p; + archive.members[i].size = n; + any = 1; + } + else + free( p ); + } + + if( !any ) + rc = commit_bytes( input, output, original, original_size, err, errsz ); + else + rc = commit_archive( input, output, &archive, archive.had_symbol_index, + err, errsz ); + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of strip_archive() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argv[0]; + const char *input = NULL; + const char *output = NULL; + unsigned char *data = NULL; + size_t size = 0; + int i; + char err[512]; + int rc = EXIT_FAILURE; + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, prog ); + return( EXIT_SUCCESS ); + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( arg, &version_action ); + return( mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE ); + } + if( !strcmp( arg, "-o" ) ) + { + if( ++i >= argc || output ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + output = argv[i]; + continue; + } + if( !strncmp( arg, "--output=", 9 ) ) + { + if( output || !arg[9] ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + output = arg + 9; + continue; + } + if( arg[0] == '-' && arg[1] ) + { + fprintf( stderr, "%s: unknown option: %s\n", prog, arg ); + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + if( input ) + { + fprintf( stderr, "%s: exactly one input file is required\n", prog ); + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + input = arg; + } + + if( !input ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + if( !output ) + output = input; + + if( cpu_melf_readall( input, &data, &size, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s\n", prog, err ); + return( EXIT_FAILURE ); + } + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + if( strip_archive( input, output, data, size, err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s: %s\n", prog, input, err ); + else + rc = EXIT_SUCCESS; + } + else + { + unsigned char *stripped = NULL; + size_t stripped_size = 0; + int changed = 0; + + if( mcpu_object_strip_data( data, size, &stripped, &stripped_size, + &changed, err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s: %s\n", prog, input, err ); + else if( commit_bytes( input, output, stripped, stripped_size, + err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s\n", prog, err ); + else + rc = EXIT_SUCCESS; + free( stripped ); + (void)changed; + } + + free( data ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-tool-options.c b/utils/mcpu-tool-options.c new file mode 100644 index 0000000..252c652 --- /dev/null +++ b/utils/mcpu-tool-options.c @@ -0,0 +1,413 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-tool-options.h" + +#define MCPU_LD_SYSTEM_CONFIG_FILE "/etc/mcpu/mcpu-ld.conf" + +static const char * +program_basename( const char *name ) +{ + const char *p; + + if( name == NULL || *name == 0 ) + return( "mcpu" ); + + p = name + strlen( name ); + while( p != name && p[-1] != '/' ) --p; + return( p ); + +} /* End of program_basename() */ + + +static int +read_optional_config( mcpu_tool_options *options, const char *filename, + int *was_read ) +{ + struct stat st; + + if( was_read != NULL ) + *was_read = 0; + + if( stat(filename, &st) != 0 ) + { + if( errno == ENOENT ) + return( 0 ); + + fprintf( stderr, "%s: cannot stat configuration '%s': %s\n", + options->progname, filename, strerror(errno) ); + return( -1 ); + } + + if( mcpu_config_read(&options->config, filename, 1) != 0 ) + { + fprintf( stderr, "%s: cannot read configuration '%s': %s\n", + options->progname, filename, strerror(errno) ); + return( -1 ); + } + + if( was_read != NULL ) + *was_read = 1; + + return( 0 ); + +} /* End of read_optional_config() */ + +void +mcpu_tool_options_init( mcpu_tool_options *options, const char *progname ) +{ + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + options->progname = program_basename( progname ); + mcpu_runtime_paths_init( &options->runtime ); + mcpu_config_init( &options->config ); + mcpu_library_search_init( &options->library_search ); + +} /* End of mcpu_tool_options_init() */ + + +void +mcpu_tool_options_free( mcpu_tool_options *options ) +{ + if( options == NULL ) + return; + + free( options->user_config_file ); + options->user_config_file = NULL; + mcpu_runtime_paths_free( &options->runtime ); + mcpu_config_free( &options->config ); + mcpu_library_search_free( &options->library_search ); + +} /* End of mcpu_tool_options_free() */ + + +int +mcpu_tool_parse_common_option( mcpu_tool_options *options, + int argc, char **argv, int *index ) +{ + const char *arg; + + if( options == NULL || argv == NULL || index == NULL || + *index < 0 || *index >= argc ) + { + errno = EINVAL; + return( -1 ); + } + + arg = argv[*index]; + + if( mcpu_version_parse_option(arg, &options->version_action) ) + return( 1 ); + + if( strcmp(arg, "--no-config") == 0 ) + { + options->no_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--dump-config") == 0 ) + { + options->dump_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--print-search-dirs") == 0 ) + { + options->print_search_dirs = 1; + return( 1 ); + } + + if( strncmp(arg, "--sys-root=", 11) == 0 ) + { + if( arg[11] == 0 ) + { + fprintf( stderr, "%s: empty path in '--sys-root='\n", + options->progname ); + return( -1 ); + } + options->sys_root = arg + 11; + options->no_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--sys-root") == 0 ) + { + if( *index + 1 >= argc || argv[*index + 1][0] == 0 ) + { + fprintf( stderr, "%s: argument missing after '--sys-root'\n", + options->progname ); + return( -1 ); + } + ++*index; + options->sys_root = argv[*index]; + options->no_config = 1; + return( 1 ); + } + + return( 0 ); + +} /* End of mcpu_tool_parse_common_option() */ + + +int +mcpu_tool_add_library_directory( mcpu_tool_options *options, + const char *directory ) +{ + if( options == NULL || directory == NULL || *directory == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_add_directory(&options->library_search, + directory) ); + +} /* End of mcpu_tool_add_library_directory() */ + + +int +mcpu_tool_add_library_path( mcpu_tool_options *options, const char *path ) +{ + if( options == NULL || path == NULL || *path == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_add_list(&options->library_search, path) ); + +} /* End of mcpu_tool_add_library_path() */ + + +int +mcpu_tool_resolve_library( const mcpu_tool_options *options, + const char *name, char **resolved ) +{ + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_resolve(&options->library_search, + options->runtime.system_library_path, name, resolved) ); + +} /* End of mcpu_tool_resolve_library() */ + + +int +mcpu_tool_resolve_library_alternatives( + const mcpu_tool_options *options, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ) +{ + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_resolve_alternatives( + &options->library_search, + options->runtime.system_library_path, + names, name_count, resolved, selected_index) ); + +} /* End of mcpu_tool_resolve_library_alternatives() */ + + +int +mcpu_tool_options_prepare( mcpu_tool_options *options, const char *argv0 ) +{ + const char *home; + const char *value; + + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + options->runtime_config_read = 0; + options->system_config_read = 0; + options->user_config_read = 0; + + if( options->sys_root != NULL ) + { + if( mcpu_runtime_paths_set_root(&options->runtime, + options->sys_root) != 0 ) + { + fprintf( stderr, "%s: cannot use MCPU system root '%s': %s\n", + options->progname, options->sys_root, strerror(errno) ); + return( -1 ); + } + } + else if( mcpu_runtime_paths_discover(&options->runtime, argv0) != 0 ) + { + fprintf( stderr, "%s: cannot determine MCPU installation root: %s\n", + options->progname, strerror(errno) ); + return( -1 ); + } + + if( mcpu_config_set(&options->config, + "MCPU_LD_SYSTEM_LIBRARY_PATH", + options->runtime.system_library_path) != 0 ) + return( -1 ); + + free( options->user_config_file ); + options->user_config_file = NULL; + home = getenv( "HOME" ); + if( home != NULL && *home != 0 ) + { + size_t n = strlen(home) + sizeof("/.mcpu/etc/mcpu-ld.conf"); + + options->user_config_file = (char *)malloc( n ); + if( options->user_config_file == NULL ) + return( -1 ); + snprintf( options->user_config_file, n, + "%s/.mcpu/etc/mcpu-ld.conf", home ); + } + + if( !options->no_config ) + { + if( options->runtime.config_file == NULL || + read_optional_config(options, options->runtime.config_file, + &options->runtime_config_read) != 0 ) + return( -1 ); + + if( read_optional_config(options, MCPU_LD_SYSTEM_CONFIG_FILE, + &options->system_config_read) != 0 ) + return( -1 ); + + if( options->user_config_file != NULL && + read_optional_config(options, options->user_config_file, + &options->user_config_read) != 0 ) + return( -1 ); + } + + value = mcpu_config_get( &options->config, + "MCPU_LD_SYSTEM_LIBRARY_PATH" ); + if( value == NULL || + mcpu_runtime_paths_set_system_library(&options->runtime, value) != 0 ) + return( -1 ); + + return( 0 ); + +} /* End of mcpu_tool_options_prepare() */ + + +int +mcpu_tool_has_information_action( const mcpu_tool_options *options ) +{ + if( options == NULL ) + return( 0 ); + + return( options->dump_config || options->print_search_dirs || + options->version_action != MCPU_VERSION_NONE ); + +} /* End of mcpu_tool_has_information_action() */ + + +static int +print_config_file_state( FILE *stream, const char *path, int was_read ) +{ + if( path == NULL ) + return( 0 ); + + if( fprintf(stream, " config: %s [%s]\n", + path, was_read ? "read" : "not found") < 0 ) + return( -1 ); + + return( 0 ); + +} /* End of print_config_file_state() */ + + +int +mcpu_tool_dump_config( const mcpu_tool_options *options, FILE *stream ) +{ + const char *library_path; + + if( options == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + library_path = options->runtime.system_library_path; + if( library_path == NULL ) + library_path = ""; + + if( fprintf(stream, "MCPU configuration:\n") < 0 || + fprintf(stream, " runtime root: %s\n", + options->runtime.installation_root ? + options->runtime.installation_root : "") < 0 ) + return( -1 ); + + if( options->sys_root != NULL ) + { + if( fprintf(stream, " configuration: disabled by --sys-root\n") < 0 ) + return( -1 ); + } + else if( options->no_config ) + { + if( fprintf(stream, " configuration: disabled by --no-config\n") < 0 ) + return( -1 ); + } + else + { + if( fprintf(stream, " configuration: enabled\n") < 0 || + print_config_file_state(stream, options->runtime.config_file, + options->runtime_config_read) != 0 || + print_config_file_state(stream, MCPU_LD_SYSTEM_CONFIG_FILE, + options->system_config_read) != 0 || + print_config_file_state(stream, options->user_config_file, + options->user_config_read) != 0 ) + return( -1 ); + } + + if( fprintf(stream, "MCPU_LD_SYSTEM_LIBRARY_PATH = %s;\n", + library_path) < 0 ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); + +} /* End of mcpu_tool_dump_config() */ + + +int +mcpu_tool_print_search_dirs( const mcpu_tool_options *options, FILE *stream ) +{ + const char *path; + size_t i; + + if( options == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + path = options->runtime.system_library_path; + if( fprintf(stream, "libraries:\n") < 0 ) + return( -1 ); + + for( i = 0; i < options->library_search.count; ++i ) + if( fprintf(stream, " %s\n", + options->library_search.directory[i]) < 0 ) + return( -1 ); + + if( path != NULL && *path != 0 && + fprintf(stream, " %s\n", path) < 0 ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); + +} /* End of mcpu_tool_print_search_dirs() */ diff --git a/utils/mcpu-tool-options.h b/utils/mcpu-tool-options.h new file mode 100644 index 0000000..f6fe356 --- /dev/null +++ b/utils/mcpu-tool-options.h @@ -0,0 +1,52 @@ +#ifndef __MCPU_TOOL_OPTIONS_H__ +#define __MCPU_TOOL_OPTIONS_H__ 1 + +#include <stdio.h> + +#include "mcpu-config.h" +#include "mcpu-version.h" +#include "mcpu-library-search.h" +#include "mcpu-runtime.h" + +typedef struct mcpu_tool_options mcpu_tool_options; +struct mcpu_tool_options +{ + const char *progname; + const char *sys_root; + int help; + int no_config; + int dump_config; + int print_search_dirs; + mcpu_version_action version_action; + char *user_config_file; + int runtime_config_read; + int system_config_read; + int user_config_read; + mcpu_runtime_paths runtime; + mcpu_config config; + mcpu_library_search library_search; +}; + +void mcpu_tool_options_init( mcpu_tool_options *options, + const char *progname ); +void mcpu_tool_options_free( mcpu_tool_options *options ); +int mcpu_tool_options_prepare( mcpu_tool_options *options, + const char *argv0 ); +int mcpu_tool_parse_common_option( mcpu_tool_options *options, + int argc, char **argv, int *index ); +int mcpu_tool_add_library_directory( mcpu_tool_options *options, + const char *directory ); +int mcpu_tool_add_library_path( mcpu_tool_options *options, + const char *path ); +int mcpu_tool_resolve_library( const mcpu_tool_options *options, + const char *name, char **resolved ); +int mcpu_tool_resolve_library_alternatives( + const mcpu_tool_options *options, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ); +int mcpu_tool_has_information_action( const mcpu_tool_options *options ); +int mcpu_tool_dump_config( const mcpu_tool_options *options, FILE *stream ); +int mcpu_tool_print_search_dirs( const mcpu_tool_options *options, + FILE *stream ); + +#endif /* __MCPU_TOOL_OPTIONS_H__ */ diff --git a/utils/mcpu-version.c b/utils/mcpu-version.c new file mode 100644 index 0000000..74e0a83 --- /dev/null +++ b/utils/mcpu-version.c @@ -0,0 +1,67 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "mcpu-version.h" + +static const char * +program_basename( const char *name ) +{ + const char *p; + + if( name == NULL || *name == 0 ) + return( "mcpu" ); + + p = name + strlen( name ); + while( p != name && p[-1] != '/' ) --p; + return( p ); + +} /* End of program_basename() */ + + +int +mcpu_version_parse_option( const char *option, mcpu_version_action *action ) +{ + if( option == NULL || action == NULL ) + return( 0 ); + + if( !strcmp( option, "--version" ) ) + { + *action = MCPU_VERSION_FULL; + return( 1 ); + } + + if( !strcmp( option, "--dump-version" ) ) + { + *action = MCPU_VERSION_DUMP; + return( 1 ); + } + + return( 0 ); + +} /* End of mcpu_version_parse_option() */ + + +int +mcpu_version_print( const char *progname, mcpu_version_action action ) +{ + int rc; + + if( action == MCPU_VERSION_FULL ) + rc = printf( "%s (MCPU) %s\n", program_basename( progname ), + PACKAGE_VERSION ); + else if( action == MCPU_VERSION_DUMP ) + rc = printf( "%s\n", PACKAGE_VERSION ); + else + { + errno = EINVAL; + return( -1 ); + } + + return( rc < 0 ? -1 : 0 ); + +} /* End of mcpu_version_print() */ diff --git a/utils/mcpu-version.h b/utils/mcpu-version.h new file mode 100644 index 0000000..5eee32f --- /dev/null +++ b/utils/mcpu-version.h @@ -0,0 +1,15 @@ +#ifndef __MCPU_VERSION_H__ +#define __MCPU_VERSION_H__ 1 + +typedef enum mcpu_version_action +{ + MCPU_VERSION_NONE = 0, + MCPU_VERSION_FULL, + MCPU_VERSION_DUMP +} mcpu_version_action; + +int mcpu_version_parse_option( const char *option, + mcpu_version_action *action ); +int mcpu_version_print( const char *progname, mcpu_version_action action ); + +#endif /* __MCPU_VERSION_H__ */ |
