Собираю здесь все что связано с разработкой на C, предпочтительно с использованием кросскомпиляции GCC
-
В папке
gccлежит скриптbuild_gcc_uknc.shс помощью которого можно собрать тулчейн для сборки проектов для УКНЦ. -
В папке
examplesлежат минимальные примеры приложений, собираемых с помощью тулчейна, получаемого скриптом из пункта 1.
Благодаря STARTFILE_SPEC и полноценной стандартной библиотеке (newlib, см.
ниже) отдельный Makefile не обязателен — gcc сам скомпилирует и слинкует
программу за один вызов, с printf/malloc/atoi и остальным stdio.h/
stdlib.h из коробки:
pdp11-uknc-rt11-gcc -std=gnu23 -fomit-frame-pointer -O2 \
-Wl,--gc-sections -o example.sav example.cВ результате получается готовый .sav файл — самостоятельный образ памяти
в формате RT-11 SAV, который можно сразу записывать на диск (rt11dsk) и
запускать на УКНЦ под RT-11 без какой-либо дополнительной обработки.
Целевой триплет — pdp11-uknc-rt11. Тулчейн (binutils + GCC + newlib)
собирается из ванильных исходников (binutils-2.45, gcc-15.2.0, newlib
снапшот, см. раздел newlib ниже) с наложением патчей из форков
wdigger/binutils-gdb,
wdigger/gcc и
wdigger/sourceware-mirror-newlib-cygwin
— build_gcc_uknc.sh скачивает их автоматически. Ниже — что именно эти
патчи меняют относительно ванильных binutils/GCC/newlib.
-
sav-pdp11(bfd/sav-pdp11.c) — новый write-only BFD-таргет, который пишет исполняемые файлы RT-11 SAV напрямую, без внешнего конвейера bin2load/lda2sav. Заголовок SAV содержит стартовый адрес, начальный SP (адрес вершины пользовательской памяти, а не точки входа — RT-11 копирует это слово в адрес042перед передачей управления) и битовую карту занятых блоков. -
Вендор
uknc(config.sub) — алиас дляpdp11-uknc-aout; при сборке GCC для этого вендора автоматически включается-mbm2(проц 1801ВМ2), если пользователь не указал-mbm1/-mbm2явно. -
Система
rt11(config.sub,gas/configure.tgt,bfd/config.bfd,ld/configure.tgt) — отдельная (отaout) система в триплете (pdp11-uknc-rt11). Форматsav-pdp11компилируется и виден только для неё (через-DBFD_SUPPORTS_SAV_PDP11вtarg_cflags);aoutостаётся побайтово ванильным binutils. -
Встроенный линкер-скрипт (
ld/emulparams/pdp11rt11.sh,ld/scripttempl/pdp11rt11.sc) — эмуляцияpdp11rt11со скриптом, зашитым вld, так что-Tбольше не требуется. -
pdp11rt11sav— эмуляция по умолчанию для rt11 —ldдля этой системы сразу производит.sav. Технически линковка всё равно идёт черезa.out-pdp11(у него есть собственный, проверенный код разрешения релокаций —pdp11_aout_link_input_section), а конвертация в SAV происходит уже после того, как файл полностью записан на диск, через новый необязательный хук эмуляцииld—after_close_output(ld/ldemul.h/.c,ld/emultempl/emulation.em, один вызов вld/ldmain.c). Так получилось после того, как выяснилось, что прямая линковка вsav-pdp11через generic BFD final-link даёт неверные релокации (например, для строковых констант). -
pdp11rt11rel— альтернативный формат вывода: родной relocatable-объект RT-11 (ld/emultempl/pdp11rt11rel.em,ld/emulparams/pdp11rt11rel.sh) — вместоa.out-pdp11пишет собственный формат RT-11 "Relocatable Object Language" (блоки GSD/TXT/RLD/ENDMOD, имена в RADIX-50), описанный в RT-11 Volume and File Formats Manual — то, что понимает штатный линкер RT-11. Устройство то же, что уpdp11rt11sav: линковка идёт через-rвa.out-pdp11(тот же проверенныйpdp11_aout_link_input_section), а готовый файл затем перечитывается обычными вызовами BFD (bfd_canonicalize_symtab/reloc) и переупаковывается в формат RT-11 — без отдельного BFD-таргета в обе стороны. Нужна именно-r:pdp11-uknc-rt11-gcc -c -O2 foo.c -o foo.o pdp11-uknc-rt11-ld -r -m pdp11rt11rel foo.o -o foo.rel
Символы длиннее 6 символов и непредставимые в RADIX-50 (например,
_в начале имени, которое добавляет сам компилятор) усекаются/заменяются на.— ограничение самого формата 1970-х, а не этой реализации.
- Поддержка процессоров 1801ВМ1/1801ВМ2 (
-mbm1/-mbm2) — Советские клоны PDP-11, отличия в системе команд от штатных моделей DEC. - Исправление кодогенерации и использование инструкции
SOBдля организации циклов сдвига — по мелочи улучшают качество генерируемого кода под эти процессоры. - Исправление реального бага компилятора: 32×32-битное знаковое
умножение (
mulhisi3) компилировалось неверно (ошибка в reload/LRA) — найдено и исправлено на реальном тестовом коде. -fstack-usage/-Wstack-usage— бэкенд pdp11 раньше молча игнорировал эти опции; теперь размер стека кадра репортится, как и на других таргетах.- Система
rt11в триплете (gcc/config.gcc) —pdp11-uknc-rt11зарегистрирован как отдельная (отpdp11-uknc-aout) система; задел под будущий порт newlib (у негоconfigure.hostвыбираетlibc/sys/<os>по системе триплета, аaoutдля этого не подходит — это формат объектных файлов, а не среда выполнения). STARTFILE_SPECдля rt11 (gcc/config/pdp11/pdp11.h,libgcc/config.host,libgcc/config/pdp11/{t-pdp11rt11,crt0rt.s, parse_args.c}) — обычная линковка дляpdp11-uknc-rt11работает без-nostartfilesи ручной линковкиcrt0rt.o:crt0rt.o/parse_args.oсобираются как часть сборки libgcc (по образцуlibgcc/config/nds32/t-nds32) и подключаются автоматически.crt0rt.sберёт SP из заголовка SAV-файла и строитargc/argvиз области командной строки, которую RT-11 сама заполняет при запуске программы (argv[0]— пустая строка-плейсхолдер: RT-11 никогда не сообщает программе её собственное имя).pdp11-uknc-aoutэту особенность не получает и продолжает использовать штатныйSTARTFILE_SPECGCC.-msoft-floatпо умолчанию для вендораuknc(gcc/common/config/pdp11/pdp11-common.cc) — настоящее железо УКНЦ не имеет сопроцессора плавающей точки, а штатный дефолт pdp11-таргета (TARGET_DEFAULT_TARGET_FLAGS) рассчитан на 11/45 с аппаратным FPU, из-за чего-msoft-floatраньше приходилось указывать вручную в каждой сборке. Теперь это дефолт для вендораuknc(иrt11, иaout);-mfpuпо-прежнему работает как явное переопределение, а для остальных pdp11-таргетов поведение не меняется.- Исправление второго реального бага компилятора: ICE в проходе
cprop_hardreg(internal compiler error: in extract_constrain_insn, at recog.cc:2783) на обычном коде видаfor (;; --a) { if (*a) break; *a = 1; }. Причина —cbranch<mode>4вpdp11.mdделит один паттерн на все режимы QI/HI/SI/DI через слишком широкий предикатgeneral_operand, хотя широкие (SI/DI) сравнения эмулируютсяcmpsi/cmpdiчерез раздельное чтение адреса дважды (по слову) — с автоинкрементной/автодекрементной адресацией это в принципе небезопасно, иcmpsi/cmpdiсами это запрещают через свои ограничения, ноcbranch<mode>4пропускал такую адресацию до проходаauto_inc_dec, который сворачивал декремент прямо в адрес сравнения. Исправлено новым предикатомcmp_operand(gcc/config/pdp11/predicates.md), который отклоняет side-effect-адреса именно для SImode/DImode — однословные (HI/QI) сравнения, где такая адресация легитимна, не затронуты.
Минимальный порт newlib для pdp11-uknc-rt11 (форк
wdigger/sourceware-mirror-newlib-cygwin,
ветка rt11-port) — даёт обычный printf/malloc/atoi/файловый I/O вместо
того, чтобы каждый пример писал свою крошечную libc с нуля.
libc/sys/rt11/syscalls.c—_read/_write/_open/_close/_lseek/_fstat/_isatty/_sbrk/_exit, плюс ENOSYS-заглушки для_kill/_getpid/_link/_unlink/_stat. Консольный ввод-вывод (fd 0/1/2) идёт через RT-11's.TTYIN/.TTYOUT(EMT), значение явно закреплено за регистром R0 черезregister ... asm("r0")— обычного ограничения"r"недостаточно, EMT читает/пишет именно R0._sbrkрастит кучу от_end(символ линкера) вверх, с ограничением по текущему SP и запасом. Стартовый код (crt0rt.o/parse_args.o) сюда не входит — он приходит из GCC'sSTARTFILE_SPEC(см. раздел GCC выше), поэтомуconfigure.hostставитhave_crt0="no"для этой системы.configure.host:sys_dir=rt11дляpdp11-uknc-rt11; отдельная веткаpdp11*в CPU-диспетчере не даёт провалиться в дефолтный кейс, который ставит-DMISSING_SYSCALL_NAMES(это подменило бы_read/_write/и т.д. на голые имена без подчёркивания, которых этот порт не определяет — используется обычная конвенция с подчёркиванием, как у d10v).- Флаги сборки (
build_gcc_uknc.sh):--enable-newlib-nano-malloc,--enable-newlib-nano-formatted-io,--disable-newlib-wide-orient— компактныеmalloc/printfи отключение поддержкиwchar_tв stdio (она здесь не нужна). Без этогоprintfв простой программе легко перевешивает за 60KB — критично на 64KB адресном пространстве PDP-11. configure/Makefile.inпересобираютсяautoreconfс закреплёнными версиями autoconf 2.69 и automake 1.15.1 (build_gcc_uknc.shсам их скачивает и собирает) — именно теми, которыми newlib сама была сгенерирована. Более новый automake меняет схему префиксов имён объектных файлов, из-за чего собственный жёстко прописанный списокMATHOBJS_IN_LIBCвnewlib/Makefile.amперестаёт совпадать с реальными именами, иlibc.aне собирается («no entry ... in archive» приar).- Два реальных бага компилятора нашлись и исправлены при разработке
порта (см. раздел GCC выше, не workaround — реальные патчи в
wdigger/gcc): reload-ICE на объединенииdoubleс меньшим целым в union (обходится-msoft-float, теперь дефолт дляuknc) иcprop_hardreg-ICE на 32-битном сравнении с автодекрементом (libc/stdlib/gdtoa-hexnan.c— единственное место в newlib, что на это напоролось; исправлено в бэкенде новым предикатомcmp_operand).
Ограничения файлового ввода-вывода (libc/sys/rt11/syscalls.c):
Ограничения, продиктованные системой:
- Размер файла известен только с точностью до блока (512 байт) — для файла, который эта сессия не писала (чтение, либо переоткрытие после close).
- RADIX-50 имя: 3/6/3 символа (устройство/имя/расширение) — жёсткий
лимит самой RT-11; если имя длиннее, выдаётся ошибка
ENAMETOOLONG. - Монитор RT-11 SJ синхронный —
.LOOKUP/.ENTER/.READW/.WRITWблокируют вызывающую программу, никакого queued/асинхронного I/O.
Осознанно принятые ограничения:
- Работают только уже резидентные устройства (
SY:,TT:,MQ:,UB:,PI:) —.FETCHне вызывается. O_APPENDпобайтово точен только в рамках одной непрерывно открытой сессии fd; после close+reopen — точность до блока, выделение не растёт за пределы исходного.LOOKUP.
Открытая проблема:
- Смешивание
puts()/printfс реальным файловым I/O иногда приводит к тихому обрыву программы; локализовано до места (послеwrite(), не породившего ни одного.WRITW), причина предположительно в самом мониторе RT-11 или в эмуляции, не в GCC.
- Бэкенд pdp11 не поддерживает global constructors (
__attribute__ ((constructor)), а также конструкторы статических C++ объектов — но C++ и так не включён, тулчейн собирается с--enable-languages=c). При попытке их использовать компилятор выдаётsorry, unimplemented: global constructors not supported on this target. Практическое следствие —-fstack-protectorне работает: кодогенерация проверки канарейки вставляется как обычно, но линковка падает сundefined reference to __stack_chk_guard/__stack_chk_fail, а не понятной ошибкой на этапе компиляции (newlib не может собрать код, который инициализирует__stack_chk_guard, — именно из-за отсутствия constructors). Это старое ограничение бэкенда, не что-то, привнесённое этим проектом; чинить не планируется, поскольку ничего в проекте пока не требует ни-fstack-protector, ни constructors как таковых. - Бэкенд pdp11 не поддерживает weak-символы — компилятор явно
предупреждает:
warning: weak declaration of 'foo' not supported(проверено на изолированном минимальном примере). Практическое следствие: newlib's_printf_floatспециально объявленweak, чтобыdtoa/mprec(арифметика произвольной точности для форматирования float, ~10KB кода) линковались только при реальном использовании%f/%e/%g— но на этом таргете weak-ссылка становится обычной сильной, иdtoa/mprecподтягиваются при любом вызовеprintf, даже если в форматной строке только%d/%ld. Для программ, которым важен размер (см.spigotвexamples), единственный обходной путь — не использоватьprintfвовсе (свойltoa/itoa+fputs/putchar). Тоже старое ограничение формата/бэкенда, не баг newlib-порта. - Бэкенд pdp11 не поддерживает
-ffunction-sections/-fdata-sections— компилятор явно предупреждает:warning: '-ffunction-sections' not supported for this target(и то же для-fdata-sections), флаги молча игнорируются. Судя по всему, из-за формата a.out — фиксированный набор секций.text/.data/.bss, а не произвольные именованные секции, нужные--gc-sections, чтобы выбрасывать отдельные неиспользуемые функции/переменные. Из-за этого-Wl,--gc-sectionsвexamples/*/Makefileреально помогает только на уровне целых объектных файлов библиотек (что линковщик и так делает при линковке из.a), а не внутри пользовательского.c-файла.