Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Электроника МС 0511 (УКНЦ)

разработка на С

GCC кросскомпиляция

Собираю здесь все что связано с разработкой на C, предпочтительно с использованием кросскомпиляции GCC

  1. В папке gcc лежит скрипт build_gcc_uknc.sh с помощью которого можно собрать тулчейн для сборки проектов для УКНЦ.

  2. В папке 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-cygwinbuild_gcc_uknc.sh скачивает их автоматически. Ниже — что именно эти патчи меняют относительно ванильных binutils/GCC/newlib.

binutils

  • 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 — эмуляция по умолчанию для rt11ld для этой системы сразу производит .sav. Технически линковка всё равно идёт через a.out-pdp11 (у него есть собственный, проверенный код разрешения релокаций — pdp11_aout_link_input_section), а конвертация в SAV происходит уже после того, как файл полностью записан на диск, через новый необязательный хук эмуляции ldafter_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-х, а не этой реализации.

GCC

  • Поддержка процессоров 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_SPEC GCC.
  • -msoft-float по умолчанию для вендора uknc (gcc/common/config/pdp11/pdp11-common.cc) — настоящее железо УКНЦ не имеет сопроцессора плавающей точки, а штатный дефолт pdp11-таргета (TARGET_DEFAULT_TARGET_FLAGS) рассчитан на 11/45 с аппаратным FPU, из-за чего -msoft-float раньше приходилось указывать вручную в каждой сборке. Теперь это дефолт для вендора ukncrt11, и 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

Минимальный порт 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's STARTFILE_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):

Ограничения, продиктованные системой:

  1. Размер файла известен только с точностью до блока (512 байт) — для файла, который эта сессия не писала (чтение, либо переоткрытие после close).
  2. RADIX-50 имя: 3/6/3 символа (устройство/имя/расширение) — жёсткий лимит самой RT-11; если имя длиннее, выдаётся ошибка ENAMETOOLONG.
  3. Монитор RT-11 SJ синхронный — .LOOKUP/.ENTER/.READW/.WRITW блокируют вызывающую программу, никакого queued/асинхронного I/O.

Осознанно принятые ограничения:

  1. Работают только уже резидентные устройства (SY:, TT:, MQ:, UB:, PI:) — .FETCH не вызывается.
  2. O_APPEND побайтово точен только в рамках одной непрерывно открытой сессии fd; после close+reopen — точность до блока, выделение не растёт за пределы исходного .LOOKUP.

Открытая проблема:

  1. Смешивание 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-файла.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages