\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@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=} & after load/preload/relocations/startup, before first guest instruction\\ \code{--after-init=} & after INIT END; при отсутствии .init --- один раз на logical empty INIT boundary\\ \code{--on-step=} & after every LIFE END\\ \code{--after-run=} & 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-:64@16,64@24 print-real-:128@32,128@48 set-int-:64@16=123,64@24=456 set-real-:128@32=1.25,128@48=2.5 \end{lstlisting} \code{} --- 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-:@} 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}