Запуск Тетриса на осциллографе из 90-х

в 9:08, , рубрики: tektronix, реверс-инжиниринг, Си

Введение

Немного о самом осциллографе. Речь пойдет про Tektronix TDS540D. Судя по серийному номеру (его все еще можно проверить на сайте производителя), осциллограф изготовлен в конце 1998 года. Несмотря на то, что осциллографу уже больше двадцати лет, его характеристики неплохи даже по нынешним меркам:

  • 4 канала

  • Частота дискретизации - 2 GS/s.

  • Полоса пропускания - 500 MHz

Недостатком являются - вес (больше 10 кг), немаленькие размеры, шум при работе. Во многом они связаны с тем, что осциллограф использует ЭЛТ в качестве экрана.

У этих осциллографов есть свое сообщество энтузиастов, которые собирают информацию об опыте их ремонта, использовании и модернизации.

Есть вот такая полезная страничка: https://w140.com/tekwiki/wiki/TDS540

Плюс много информации по осциллографом этой линейки моно найти здесь: www.eevblog.com

Свой осциллограф я купил на eBay в 2018 году специально для изготовления самодельного лидара. Заказывать устройство с ЭЛТ из США с доставкой "Почтой России" - тот еще риск, но все же осциллограф доехал нормально, во многом за счет качественной упаковки. Вместе с доставкой он обошелся мне в примерно 500$, что значительно дешевле новых осциллографов с аналогичными характеристиками.

Программирование осциллографа

Что представляет собой этот осциллограф с программной точки зрения?

Так выглядит плата процессора осциллографа

Так выглядит плата процессора осциллографа

Если почитать форумы, то можно выяснить, что он построен на базе процессора Motorola 68EC040, используемая ОС - RTOS VxWorks, загружаемая из Flash памяти. Единственный доступный дисковый накопитель - дисковод FDD.

Главное назначение дисковода - сохранение осциллограмм и настроек осциллографа.
Но также, судя по информации с форумов, с дискеты можно запускать простые скрипты, написанные на несколько экзотическом языке - Smalltalk. Замечу, что в инструкции на сам осциллограф указано, что пользователь может запускать дополнительные приложения с дискеты, но никакой информации о том, как их создавать, какое использовать API - в открытом доступе нет. Энтузиасты, в основном используют эти скрипты, чтобы сохранять "сырые" данные NVRAM на дискету - это важно, так как в качестве NVRAM используются дурацкие микросхемы Dallas со встроенной батарейкой, которую невозможно заменить. Кроме того, используя скрипты, можно программно активировать некоторые аппаратные и программные функции (к примеру, FFT и больший объем памяти), отключенные с завода.

Но есть еще более интересная информация, которая чудом дожила до наших ней - на старшую модель этих осциллографов (имеющую HDD и больший объем ОЗУ) можно было установить виртуальную машину Java, при этом в сети сохранился софт инсталлятора!
А зачем вообще устанавливать Java на осциллограф? Чтобы можно было разрабатывать высокоуровневый софт для осциллографа. К сожалению, в сети не сохранилось примеров таких приложений (кроме одного), но судя по имеющейся информации, они точно были.
Точно было приложение для анализа джиттера (TDSJIT1), анализа силовых линий (TDSPWR1), анализа линий связи (TDSCPM1).

Пример скриншота приложения из инструкции:

Приложение TDSPWR1

Приложение TDSPWR1

Про то, как писать такие приложения, опять же, никакой информации нет. Сохранилось, правда, одно единственное Java приложение - TDSPRT1, предназначенное для более удобного управления процессом печати данных прямо с осциллографа.

Таким образом, у меня имелись:

  • Бинарные коды виртуальной Java машины, не предназначенные для запуска на имеющемся осциллографе

  • Упакованная программа на Java

  • Несколько простых скриптов, не делающих чего-либо интересного.

Первое, что я попытался сделать - все же запустить Java на своем осциллографе.
В состав инсталлятора JAVA входят файлы:

TDSRTE1

TDSRTE1

Файл install.app - как раз скрипт инсталлятора, файлы с расширением APP можно запустить из меню самого прибора.

Содержимое файла install.app:
nulldev = open("/null",3,0666) 
taskSpawn "Redirect",1,0,0x4000,ioGlobalStdSet,1,nulldev 
taskDelay(360) 
GpibInput("WAITICON OPEN")

mkdir "hd0:/APP" 
mkdir "hd0:/APP/TDSRTE1" 
mkdir "hd0:/APP/TDSRTE1/temp"

ld < fd0:/safecopy.o

safecopy "fd0:/extcp.o","hd0:/app/tdsrte1/extcp.o" 
safecopy "fd0:/java68k.o","hd0:/app/tdsrte1/java68k.o" 
safecopy "fd0:/libjit.o","hd0:/app/tdsrte1/libjit.o" 
safecopy "fd0:/nigpib.o","hd0:/app/tdsrte1/nigpib.o" 
safecopy "fd0:/patch.o","hd0:/app/tdsrte1/patch.o" 
safecopy "fd0:/rte1.bat","hd0:/app/tdsrte1/rte1.bat" 
safecopy "fd0:/version.dat","hd0:/app/tdsrte1/version.dat" 
safecopy "fd0:/logo.bin","hd0:/app/tdsrte1/logo.bin" 
safecopy "fd0:/rtestart.bat","hd0:/startup.bat" 
safecopy "fd0:/ossa.bat","hd0:/ossa.bat"

GpibInput("mess:box 80,160,450,220") 
GpibInput("mess:show "n Core RTE components updated.n Remove disk and power cycle Instrument."") 
GpibInput("WAITICON CLOSE")

Фактически, программа создает на диске несколько папок и копирует туда файлы с дискеты, заодно переименовывая их. В конце программа показывает окошко с текстовым сообщением.

А вот содержимое другого файла - rte1.bat, который вызывается уже в процессе запуска осциллографа, после того, как Java-машина установлена:

Содержимое файла rte1.bat:
# TDS RTE1.1 startup file.
nulldev = open("/null",3,0666)
taskSpawn "Redirect",250,0,0x4000,ioGlobalStdSet,1,nulldev

ld < hd0:/app/tdsrte1/java68k.o
ld < hd0:/app/tdsrte1/libjit.o
ld < hd0:/app/tdsrte1/nigpib.o
ld < hd0:/app/tdsrte1/extcp.o

javaConfig()
javaVxMaxMemSizeSet (0x75c000)
javaDefaultMaxHeapSizeSet(0x300000)
javaDefaultStackSizeSet(0xa000)
javaCompilerSet("jit");
rteInit()
taskSpawn "Redirect2",250,0,0x4000,ioGlobalStdSet,1,nulldev
< hd0:/appstart.bat

Видно, что программа загружает четыре объектных файла в память, и далее - настраивает виртуальную машину Java. Выходит, фактически, получается всего лишь модифицировать этот скрипт, чтобы загрузка происходила с дискеты, а не HDD?

Я попробовал сделать это, но никакого видимого результата не получил. Немного доработал скрипт - добавил в него вывод сообщений после загрузки каждого объектного файла - и выяснил, что загрузка проваливается на файле "extcp.o", но с чем это связано - непонятно. Тупик?

Отладочный порт

На тех же форумах мне встречались упоминания того, что у этой линейки осциллографов есть специальный отладочный порт, куда осциллограф выводит логи, и куда можно отравлять команды для управления им. Для экономии порт представляет собой просто краевой разъем на печатной плате (PCB Gold Fingers), куда выведена часть параллельной шины системы.

Отладочный разъем. Имеет по 10 контактов с каждой стороны. Первый пин - отмечен стрелкой.

Отладочный разъем. Имеет по 10 контактов с каждой стороны. Первый пин - отмечен стрелкой.

Чтобы получить к нему доступ, не нужно снимать крышку с осциллографа - он находится под специальной съемной панелью сзади (вместо простой панели там может быть панель разъемов RS232/LPT).

К этому разъему можно подключить микросхему MC68681 - приемопередатчик UART, и именно этот UART и можно использовать для связи с осциллографом. Подробнее про устройство и схему - в обсуждении на форуме. Автор обсуждения там использовал микросхему в корпусе PLCC44, я же использовал микросхему в корпусе DIP-40 - ее легче было достать, да и панелька для нее дешевле. Однако, в таком случае пришлось несколько изменить схему.

Разъем для подключения к плате осциллографа сделал из кабеля 5-дюймового дисковода - его пришлось заметно порезать.

Внешний вид собранного адаптера отладочного порта.

Внешний вид собранного адаптера отладочного порта.

Важно подтянуть вход RX микросхемы к 5В через резистор - без этого, я так понимаю, неподключенный вход ловил помехи, и это мешало прошивке осциллографа нормально стартовать.

Эксперименты

После запуска осциллограф выдает довольно много информации в отладочный порт:

Пример лога:
RUNNING FROM DRAM.
DRAM test passed.


        Bootrom Header Checksum passed.
        Bootrom Total Checksum passed.
        BootRom Check Sum passed.
        Bus Error Timeout test passed.

Kernel Diagnostics Complete.

Calling SDM (monitor) Routine.

        Enabling Bus Control register. Value = 0x67
        IMR 1 Register test passed.
        Misc. Register test passed.
        Timer Interrupt test (Auto-Vector) passed.
        NVRam DSACK test passed.
        NVRam Write protected.
        Flashrom DSACK and JumpCode test passed.
        Flashrom Checksums passed.

Bootrom Diagnostics Complete.


DipSwitchValue: 0


Skipping boot loader.
Transferring control to FlashROM.

No PCMCIA option board detected.
FLOPPY: Detected

Adding 7007 symbols for standalone.


CPU: 68EC040.  Processor #0.
Memory Size: 0x1000000.  BSP version 1.0.

Executing Diagnostics
-> Start Power-On Diag Sequence
hwAccountant probe routines
  Probe for unexpected pending ints
  Dsp Instr mem size
  Dsp D2 mem size
  Dsp D1 mem size
  Dsy Vect0 mem size
  Dsy Vect1 mem size
  Dsy Wfm0 mem size
  Dsy Wfm1 mem size
  Dsy Text0 mem size
  Dsy Text1 mem size
  Acq number of digitizers
  Acq mem size
  Cpu timer interval uSec
  Cpu Dram size
  NvRam mem size
  Limit to 1GS/sec presence
  Opt Math Package presence
  Opt Telecom Masks presence
  Opt 3C presence
  Opt 4C presence
  Opt RS232/ Cent presence
  Opt 1M presence
  Opt 2M presence
  Acq Intlv Cal Id presence
  Opt TvTrig presence
  Opt TvTrig index
  Dsy color presence
  Opt floppy drive presence
  Opt hard drive presence
  Acq number of user channels
dspForcedBus ................... pass
cpuDiagD2MiscReg ............... pass
cpuDiagDSPIntMaskReg ........... pass
cpuDiagDsyAccess ............... pass
dsp68kMemTest .................. pass
cpuDiagFIFOMem ................. pass
dspRunVerify ................... pass
dspBusRequestTest .............. pass
dspImplicitBusAccess ........... pass
dspTristarMemTest .............. pass
dsyDiagPM100Reg ................ pass
dsyPM011RegMem ................. UNTESTED
dsyLCSRamdacRegMem ............. UNTESTED
dsyMonoRamdacRegMem ............ pass
dsyVGARamdacRegMem ............. pass
dsyDiagPPRegMem ................ pass
dsyDiagRasRegMem ............... pass
dsyDiagRegSelect ............... pass
dsyDiagAllMem .................. pass
dsySeqYTModeV0Intens ........... pass
dsyDiagSeqXYModeV1 ............. pass
dsyRastModeV0Walk .............. pass
dsyRastModeV1Attrib ............ pass
dsyWaitClock ................... pass
dsySplashScreen ................ pass
cpuDiagAllInts ................. pass
nvLibrariansDiag ............... pass
calLibrarianDefaultCk .......... pass
dspForcedBus ................... pass
acqProcThermistor .............. pass
twoGHz50OhmOvldConf ............ UNTESTED
ch1EdgeTrigDiag ................ pass
lineTrigDiag ................... pass
digHFStepConf .................. pass
fpDiagConf ..................... pass
optDiagPM110Reg ................ pass
optDiagFloppyCacheMem .......... pass
optDiagFloppyControllerIO ...... pass
optDiagFloppyDrive ............. pass
optRS232DuartIO ................ UNTESTED
optRS232DuartIntLoop ........... UNTESTED
optCentronCntrlReg ............. UNTESTED
optTv8bitRegDiag ............... UNTESTED
optTvShiftRegDiag .............. UNTESTED
optTvXparentCodes .............. UNTESTED
optTvDontCareCodes ............. UNTESTED
optTvEdgeAndLevel .............. UNTESTED
optTvSyncLevels ................ UNTESTED
Executing Smalltalk
hwAccountant probe routines
  Probe for unexpected pending ints
  Dsp Instr mem size
  Dsp D2 mem size
  Dsp D1 mem size
  Dsy Vect0 mem size
  Dsy Vect1 mem size
  Dsy Wfm0 mem size
  Dsy Wfm1 mem size
  Dsy Text0 mem size
  Dsy Text1 mem size
  Acq number of digitizers
  Acq mem size
  Cpu timer interval uSec
  Cpu Dram size
  NvRam mem size
  Limit to 1GS/sec presence
  Opt Math Package presence
  Opt Telecom Masks presence
  Opt 3C presence
  Opt 4C presence
  Opt RS232/ Cent presence
  Opt 1M presence
  Opt 2M presence
  Acq Intlv Cal Id presence
  Opt TvTrig presence
  Opt TvTrig index
  Dsy color presence
  Opt floppy drive presence
  Opt hard drive presence
  Acq number of user channels
can't open input 'fd0:/startup.bat'
  errno = 0x380003 (S_dosFsLib_FILE_NOT_FOUND)

Smalltalk/V Sun Version 1.12
Copyright (C) 1990 Object Technology International Inc.

В основном тут идут данные тестирования периферии и заканчиваются они переходом в консоль Smalltalk/VxWorks.

Можно, например, посмотреть информацию о системе:

-> _printTekLogo
            VxWorks version 5.2.
            Columbia Proc. Brd. Rev
            System Ram Size = 0, Proc ID = 0x0
            NvRam Size = 131072, Clock Interval = 13108 usecs,
            Bootline Storage Size = 220 stored @0x4000024
            Flash ID = 0x0, Flash Wait states = 0 (Misc. reg = 0x0),
            KERNEL: WIND version 2.4. GCC COMPILED
            Copyright 1991-2001, Tektronix, Inc.
            made on: Mon Sep 21 14:58:49 PDT 1998.

Или посмотреть список запущенных задач:

-> i

  NAME        ENTRY       TID    PRI   STATUS      PC       SP     ERRNO  DELAY
---------- ------------ -------- --- ---------- -------- -------- ------- -----
tExcTask   _excTask      5ffca24   0 PEND        5051f00  5ffc98c       0     0
tLogTask   _logTask      5ffa124   0 PEND        5051f00  5ffa088       0     0
tShell     _shell        5ff6540   1 READY       5029a48  5ff6208       0     0
gpibIHTask _gpibIHTask   5386380   4 PEND        5051f00  5386300       0     0
fifoTask   _fifoTask     5ffdf24   9 PEND        50256c8  5ffded8       0     0
causeEnable_causeEnable  5fffcac  10 PEND        5051f00  5fffc1c       0     0
rtcTicker  _dateTimeQue  538a134  51 DELAY       501ae6e  538a0e8       0    30
priority_no_priority_no  53876e4  60 PEND        50256c8  53876a0       0     0
grun Reboot_rebootGrun   5381d2c  65 PEND        50256c8  5381ce4       0     0
GPIB monito_GpibMonitor  5385a20  68 PEND        50256c8  53859c4       0     0
GPIB reboot526a094       53850e0  68 PEND        50256c8  538509c       0     0
trigStatus _trigStatusQ  538aad0  69 DELAY       501ae6e  538aa90       0     9
GPIB parser_Grun         53847a0  70 PEND        50256c8  5384428  3d0001     0
V main     _main         53906f4 100 PEND        50256c8  5390664  3d0002     0
calThread3 _calRunner    5fff290 102 PEND        50256c8  5fff250       0     0
calThread2 _calRunner    538de54 102 PEND        50256c8  538de14       0     0
calThread1 _calRunner    538cd28 102 PEND        50256c8  538cce8       0     0
calThread0 _calRunner    538bbfc 102 PEND        50256c8  538bbbc       0     0
evalProcess_eval_loop    5388dd0 120 PEND        50256c8  5388d90  3d0002     0

Можно также смотреть и редактировать память, делать некоторые другие вещи, связанные с ОС, ну и запускать любые команды, используемые в вышеупомянутых скриптах.

Далее я попробовал опять загрузить виртуальную машину Java (теперь делать скрипты для дискеты больше не нужно, все команды можно отправлять из консоли).

В итоге, как оказалось, команда ld < fd0:/app/tdsrte1/extcp.o выдала ошибку:

undefined symbol: _rdrfunc
ld error: error reading file (errno = 0x1c0001).

Что вообще это значит?

Тут стоит подумать над тем, что же вообще делает команда "ld". LD - команда VxWorks, предназначенная для загрузки программы в память. Подробнее про нее можно почитать здесь. Но этот процесс - не простое копирование, а фактически, динамическая компоновка (т.е. dynamic linking). В процессе загрузки этот компоновщик устанавливает связи между адресами в загружаемой программе и символами (функциями и переменными), уже загруженными в память ОС. Для этого используется таблица символов ОС, содержащая, в том числе, текстовые названия всех символов. Символы загружаемой программы, объявленные глобально, также добавляются таблицу символов ОС.

Таким образом, появившаяся ошибка говорит о том, что в процессе загрузки компоновка провалилась - ld не смог найти в таблице символов ОС функцию "_rdrfun". Почему же это произошло? Я не сразу смог выяснить, в чем дело.

Хорошо, что текстовые названия символов присутствуют в "сыром" виде в объектных файлах (это я выяснил, из любопытства открыв из в текстовом редакторе). Проверка показала, что ни в одном другом из трех объектных файлов, функция "_rdrfun" не упоминается.

В составе VxWorks есть специальная команда, которая ищет в таблице символов ОС информацию про указанные символы и выводит ее в консоль: lkup "symbol_name". В конкретно этом случае эта команда ничего не выдала, то есть "_rdrfun" действительно не является частью ОС.

Может, она не загрузилась по какой-то причине? У меня были примеры прошивок для этой линейки осциллографов, и я заглянул текстовым редактором в них. Поиск "_rdrfun" ничего не дал. Похоже, что нужно искать где-то в другом месте.

Тут я решил внимательней присмотреться к остальным файлам.
DAT файлы внутри практически пустые, и ничего интересного из себя не представляют.
А что у нас в скрипте "ossa.bat"?

#RESOURCES: MEMREQ=0x0: WAIT=1: 
ld < hd0:/app/tdsrte1/logo.bin
jplogosetup()

Оказывается, файл logo.bin - вовсе не простая картинка в каком-то проприетарном формате, а объектный файл! И при помощи текстового редактора я обнаружил, что он-то и содержит "_rdrfun".

Ну а функция jplogosetup() в скрипте запускает отрисовку изображения логотипа.

Судя по всему, осциллограф запускает скрипт "hd0:/ossa.bat" в процессе загрузки (конечно же, если у него есть HDD).

Я попробовал загрузить вручную файл logo.bin в память, а потом - запустить jplogosetup(), и в результате получил такое изображение:

Java RTS Logo

Java RTS Logo

Осциллограф, правда, после этого завис)

Этот файл показался мне довольно полезным - имеющаяся в нем функция jplogosetup() явно умела выводить графику.

После того, как я загрузил в память logo.bin, загрузка файла extcp.o тоже прошла без проблем. Замечу, правда, что загрузка всех пяти файлов с дискеты занимает ну очень уж много времени - несколько минут.

Дальше попытался инициализировать Java и запустить программу на выполнение, но с этим возникли проблемы:

Логи
-> rteInit()
sizeof(gbStruct) = 2
dlsym:error,symbol AppsGeneralEventQueue not found in symbol space 100626796
0x5ff6540 (tShell): Apps General Purpose Event Queue not registered in this system

dlsym:error,symbol AppsVariableEventQueue not found in symbol space 100626796
0x5ff6540 (tShell): Apps Variable Event Queue not registered in this system
value = 0 = 0x0

-> waitForJava()
..value = -1 = 0xffffffff = _Java_libzip.o

-> extendJavaClassPath "fd0:/tdsprt1.jar"
value = 87555464 = 0x537fd88 = _classpath + 0x30c

-> ExternalCommandProcessorWrite("tek.apps.dso.eprint.EnhancedPrintApp.main")

dlsym:error,symbol AppsEventRoute not found in symbol space 100626796
0x5ff6540 (tShell): This Firmware version is not event route enabled
value = 0 = 0x0
->
dlsym:error,symbol GetParentMepeState not found in symbol space 100626796
0x5f17e6c (Java-Startup): This Firmware version is not Mepe Sync enabled
Unable to initialize threads: cannot find class java/lang/Thread

0x56e0e70 (tJmain): memPartAlloc: block too big - 3145760 in partition 0x53b8308.
task 0x56e0e70 (tJmain): pagedPartition id 0x05a9d18c status ppLib_OK result ppLib_ALLOCATOR_EXHAUSTED
pagedPartitionMalloc(): request for 3145728 bytes failed.
**Out of memory, exiting**

Явно есть какие-то ошибки в процессе запуска Java, и есть ошибки в коде запуска приложения. Последние, судя по всему, связаны с тем, что у моего осциллографа слишком мало памяти. Это можно было бы поправить, изменив настройки виртуальной машины Java - но я уже как-то устал от ковыряний с Java.

К тому моменту я распаковал содержимое java-программы tdsprt1.jar, и выяснил, что вся связь между логикой работы пользовательского приложения и софтом самого осциллографа в виртуальной машине Java организована через виртуальные GPIB-сообщения - то есть программа пользователя формирует текстовые строки-команды, Java-машина перебрасывает их в прошивку осциллографа, прошивка их парсит и выполняет уже свои собственные функции. Звучит не очень быстро, и пользователь ограничен только теми командами, что поддерживаются парсером GPIB. Плюс сама по себе загрузка виртуальной машины в память и ее запуск занимали слишком много времени.

А что делать, если хочется большей гибкости и скорости? Написать свой собственный объектный файл, и запустить функцию из него на исполнение!

Объектные файлы

Что же представляют собой объектные файлы, используемые в VxWorks, которые загружает команда "ld"? Потратив некоторое время на поиски в Google, я выяснил, что тут используется довольно старый формат "A.OUT". Радует, конечно, что этот формат - открытый, VxWorks не стали создавать свой проприетарный формат. Проблема, правда, в том, что формат этот - реально старый. Современные компиляторы о нем не знают.

То, что это действительно "a.out", можно проверить в Linux утилитой "file":
file safecopy.o
safecopy.o: a.out SunOS mc68020 executable not stripped

Для начала, я решил попробовать дизассемблировать код нескольких файлов, входящих в состав дистрибутива Java-машины - safecopy.o (1 202 байт) и logo.bin. Для этого я использовал Open Source программу Ghidra.

Она открывала файл, могла его дизассемблировать, иногда даже находила безымянные функции и циклы в них, но явно было видно, что анализ явно неполный. Объектный файл содержит в себе названия все своих и внешних символов, так где же они?

Все оказалось довольно просто - Ghidra не поддерживает "A.OUT", и воспринимает такие файлы просто как бинарный код. Однако тут мне повезло - на Github нашелся fork c доработками, добавляющими поддержку "A.OUT": обсуждение здесь.

После того, как я его скачал, и собрал Ghidra из исходников, она действительно начала "видеть" названия некоторых функций - тех, что являются экспортируемыми, т.е. передаются в ОС при загрузке файла. А вот импортируемые из ОС функции (printf, например) и переменные Ghidra не обнаруживала.

В итоге мне пришлось заглянуть в исходные коды самой Ghidra (конкретно в те, что были изменены в форке). Там я и выяснил, что добавление импортируемых символов в Ghidra просто не реализовано в этом форке. Пришлось несколько доработать код, используя в качестве примера уже имеющийся в составе Ghidra код для анализа ELF файлов. В результате мне удалось заставить Ghidra отображать и импортируемые символы, но за получившийся код несколько стыдно(

Так выглядит пример восстановленной функции из файла safecopy.o
int _safecopy(char *param_1,char *param_2)
{
  bool bVar1;
  int iVar2;
  char *__dest;
  int iVar3;
  
  iVar3 = 1;
  do {
    iVar2 = _chcopy(param_1,param_2);
    if (iVar2 == 0) {
      return 0;
    }
    _printf((char *)0x0);
    _GpibInput(0x20);
    __dest = (char *)_memPartAlloc(__memSysPartId,0x10);
    _bzero(__dest,0x10);
    _strcat(__dest,"bad_");
    _strcat(__dest,param_2);
    _rename(param_2,__dest);
    bVar1 = iVar3 < 4;
    iVar3 = iVar3 + 1;
  } while (bVar1);
  _printf("Copy Failed retrysn");
  return iVar2;
}

В принципе, происходящее внутри функции довольно понятно. Используя logo.bin, можно посмотреть, как реализована работа с графикой (замечу, что она сделана очень запутанно).
Используя эти знания, уже можно написать простую программу, выводящую графику на экран, или работающую с консолью.

Но что делать с другими функциям осциллографа, как, например, опрашивать кнопки?

Анализ прошивки

Чтобы выяснить, что же происходит в прошивке осциллографа, можно дизассемблировать и ее. В интернете есть даже статья о том, как это сделать на более старой модели осциллографа. В этой статье автор считал прошивку из осциллографа, используя GPIB. Я решил пойти по более простому пути и просто вычитал содержимое Flash на дискету, используя команды в консоли осциллографа. Данные я вычитывал блоками по 500 Кбайт, вся Flash память имеет размер 4 Мбайт.

Сам скрипт у меня не сохранился, основан он был на базе уже имеющегося: https://github.com/ragges/tektools/tree/master/tdsNvramFloppyTool

Получив прошивку, я открыл ее в Ghidra - и она действительно смогла обнаружить большое число функций и переменных, правда, безымянных. При этом в текстовом редакторе я мог видеть очень много текстовых названий функций, что логично, так они должны каким-то образом попасть в таблицу символов ОС. В статье выше автор выяснил адреса функций, вызывая команду VxWorks lkup - она может выдать названия и адреса всех функций, имеющихся в таблице символов ОС. Правда, выдает она их частями, требуя от пользователя нажимать Enter, да и перебирать все буквы алфавита в двух регистрах довольно муторно. Автор же в статье не раскрыл, как именно он получил всю таблицу символов - собирал ее вручную, или как-то автоматизировал процесс.

Я нашел более простой способ заполнить таблицу символов в Ghidra - нашелся вот такой набор скриптов: https://github.com/PAGalaxyLab/vxhunter , из которых для меня был самым полезным "vxhunter_firmware_init.py" - он действительно переименовал все функции и переменные, и правильно определил смещения адресов в памяти (как оказалось, стартовый адрес - 0x05001000). В итоге, результат выглядит так:

Ghidra (Firmware)

Ghidra (Firmware)

Здесь уже видно огромное количество функций, причем можно видеть, какие у них аргументы, и как конкретно они используются. Замечу, правда, что Ghidra не смогла дизассемблировать весь код целиком - больше количество найденных функций ни откуда не вызываются (точнее, Ghidra не нашла их вызовов), часть бинарного кода не повергалась дизассемблированию.

Я довольно быстро смог найти функции, отвечающие за простую графику (_drawHLine, _drawVLine - могут рисовать и стирать линии, _pokeDiag - рисует наклонные линии), нашел функции отвечающие за очистку экрана (тут очень повезло, что софт осциллографа не перерисовывает экран постоянно, фактически, всегда перерисовывается только та область, где требуется изменение графики, и обычно это связано только с действиями пользователя).

Графика есть, а что же с кнопками? На передней панели их целая куча. Как оказалось, с ними все сложнее. Вот со светодиодами - нет проблем, функция "_frontPanelLed", переключающая светодиоды, нашлась легко. А вот с кнопками так просто нужные функции не находились.

В конце концов удалось найти функцию _getFrontPanel(), которая читает адрес 0x0558a274. Как именно прошивка использует эту функция - непонятно, так как в Ghidra цепочка вызовов функций быстро оборвалась. Тем не менее, удалось выяснить, что значение по адресу 0x0558a274 действительно меняется и есть прямая зависимость его от нажатых кнопок. При повторном нажатии кнопки значение не меняется, но его можно затереть нулем - это не нарушает работу прошивки.

А вот как выводить текст на экран, я так и не смог выяснить. Казалось бы - огромная часть кода должна быть связана с выводом текста на экран, но нет, я потратил на поиски кучу времени, и провалился. Похоже, что вывод текста как-то тесно интегрирован с ОС.
Замечу, что меня не удивит, если часть кода осциллографа тоже написана на Smalltalk, а текст скриптов запакован. Как минимум, я не смог найти тексты, выдаваемые при нажатии кнопки HELP, зато нашел символ (скорее всего - массив) с названием "_Packed_text", и соответственно, функцию _unpack_text().

Для вывода небольших текстовых сообщений можно использовать специальные команды GPIB:

_GpibInput("mess:box X1,Y1,X2,Y2"); //Создает окно сообщения
_GpibInput("mess:show " TEST""); //выводит указанный текст в окно
_GpibInput("mess:state OFF"); //Заакрывает окно

Используя команды GPIB, очевидно, можно получить доступ к большинству функций осциллографа, благо документация о том, как управлять осциллографом по GPIB есть в открытом доступе.

Создание собственных объектных файлов

Для того, чтобы создать объектный файл из С кода, нужны компилятор и ассемблер. В данном случае - под довольно старый процессор 68K и плюс с поддержкой формата "a.out". Раньше такая функциональность была в GCC, но к нынешнему времени ее удалили, насколько я смог понять. Так что я потратил довольно много времени, пытаясь раздобыть себе компилятор с ассемблером. Пытался искать варианты старого софта для древних версий Windows, пытался собрать в Linux старые версии Binutils+GCC (GCC собрать не удалось, вероятно, мой системный компилятор был слишком новый).

В итоге, удалось организовать следующую связку, работающую в Windows:

  • Binutils (содержит в себе ассемблер as): Его я установил, используя найденный скрипт для MSYS2: https://gist.github.com/WillSams/f592f9d494b51119945440f7e91079b0
    К сожалению, скрипт рассчитан для сборки варианта компилятора, работающего с ELF файлами, так что мне пришлось его модифицировать, заменив --target=m68k-elf на --target=m68k-unknown-aout . В итоге Binutils собрался, а GCC - нет.

  • GCC. Его я взял тут: ссылка - вариант m68k-elf-gcc4.8.0. Так как этот gcc выдает ELF, то приходится запускать его в режиме формирования ассемблерного кода (ключ командной строки "-S").

В результате сборка выглядит следующим образом:

Запускаем компилятор в командной строке Windows:

C:sysgccm68k-elfbinm68k-elf-gcc.exe D:{path_to_file}tetris.c -o D:{path_to_file}tetris.s -S
В результате получается программа на ассемблере. Её нужно вручную отредактировать - удалить все упоминания слова ".section" (ELF их поддерживает, "a.out" - нет). Также я переименовывал все упоминания ".rodata" в ".data". Все глобальные переменные нужно инициализировать - иначе ассемблер тоже не понимает файл от компилятора

После этого я копировал файл ассемблера в папку C:msys64optm68kbin - там установлен ассемблер (правильней было бы настроить пути в MSYS2, но я поленился).

Далее - нужно в командной строке MSYS2 перейти в папку ассемблера и запустить его:

cd /opt/m68k/bin/
./m68k-unknown-aout-as.exe tetris.s

В результате в этой же папке появляется файл с названием "a.out" - это и есть получившийся объектный файл.

Проверка показала, что этот файл без проблем можно загрузить в память осциллографа командой "ld", функции, объявленные в нем, добавляются в таблицу символов ОС, и их можно спокойно запускать на исполнение. Никаких особых требований к исходному файлу на С нет.

Тетрис

Я решил, что запуск игры Тетрис на осциллографе сможет хорошо продемонстрировать возможности свободного программирования осциллографа, и не потребует серьезных затрат времени.

Чтобы не изобретать велосипед, в качестве образца взял этот пример - https://github.com/c0pperdragon/ArduinoGameConsole

Конечно, его пришлось радикально переделать в плане вывода графики. Код получившейся программы приведен ниже - в ссылке на Github.

Вся графика отрисовывается только линиями, стирание объектов - тоже за счет рисования "черных" линий.

Управление движением фигур - четырьмя кнопками на цифровой клавиатуре.

Важно, чтобы программа не блокировала CPU на все время - иначе управление осциллографом перестаёт работать, поэтому задержки нужно организовывать, используя системную функцию _taskDelay().

Для удобства запуска программы с дискеты я написал простейший скрипт, который можно запустить через меню осциллографа.

Результат выглядит так:

Tetris запущен на осциллографе

Tetris запущен на осциллографе

А вот так программа выглядит в работе (на видео все изображение мерцает, но глазами это не видно):

Напоследок

Существует старшая модель осциллографа, с цветным экраном (что интересно, ЭЛТ там такой же, но перед экраном установлен специальный ЖК-светофильтр, управляемый электроникой): подробнее про технологию. Плата и прошивка там такие же, только ОЗУ на плате установлено больше. Есть даже инструкция, как переделать ЧБ осциллограф в цветной: https://www.eevblog.com/forum/repair/conversion-tektronix-tds500-to-tds700-color-oscilloscope/

Я заметил, что если вызвать функцию _initFullColorPalettes(100,100); , то осциллограф действительно меняет палитру, и на подключенном VGA мониторе появляется цвет, там где раньше были градации серого:

Запуск Тетриса на осциллографе из 90-х - 9

Интересно, что в еще более старом осциллографе HP 54600 Тетрис был частью прошивки (Easter Egg): https://imgur.com/a/343yN


Ссылка на Github: https://github.com/iliasam/tektronix_experiments

Автор:
iliasam

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js