Трапився мені1 цікавий артефакт епохи. Як відомо, в кінці 20-го сторіччя виникла проблема – багато коду покладалося на двоцифрову дату. З одного боку, проблема була серйозна – що б там ‘‘нехтувачі’’ не казали, з іншого – як завжди, дуже перебільшена в медіа2. Детальніше див., наприклад, вікіпедію та статтю “What was the Y2K problem and what was the solution?”.
Опис
![]() |
---|
Фото взято тут. |
Єдиний вміст плати – ROM, з модулем BIOS, адресу якого можна вибрати перемичкою3. Я випробував її на кількох комп’ютерах та дизасемблював код і поверхнево його проаналізував. Також, колеги підказали ще один такий пристрій з Західного ринку – його код теж (частково) дизасемблював.
Плата – української розробки. Скориставшись посиланням з інструкції, знайшов на вебархіві сайт виробника:
- Кто мы есть: ‘‘*Группа компаний “Компьютеринтерсервис” - системный интегратор, имеющий семилетний опыт деятельности на рынке корпоративного заказчика. <пропущено> В 1999-ом году на базе "Комьютеринтерсервис" был разработан и внедрен в серийное производство контроллер "BIOS-Y2000 PS", который помог решить аппаратную часть "проблемы-2000". Технические характеристики контроллера обеспечили группе компаний получение государственного заказа. Общий объем реализации составил более 70 000 штук.*''пропущено>
- Див. також архів новин.
Крім того, засновник та директор Львівського Музею Комп’ютерних Технологій каже, колись пиячив з розробником, але зараз, на жаль, контактів з ним немає.
Аналіз коду
Ремарка: модуль BIOS починається з 0x55, 0xAA, далі 8 – кількість 512-байтових блоків у модулі. Має бути вирівняним на границю 2 Кб. З третього байту починається код. Системний BIOS сканує адреси від 0xC0000 до 0xF4000 (старих, по 0xE0000 на нових), з кроком 2 Кб, зустрівши таку сигнатуру – передає керування.
Зміщення коду плати відносно початку модуля – 0x100, більшість байт між початком модуля і 0x100 – нульові і не використовуються. Можливо, код генерувався як COM-файл – із ‘‘звичним’’ org 100h, а місця на мікросхемі вистачало, то й не заморочувалися…
Виглядає (подробиці див. далі), плата вважає – якщо рік менший, ніж 1980-й то насправді це 20xx, інакше – 19xx, і намагається повідомити про це RTC.
- Код дивний, видається неохайним.
- Містить помилки, наприклад, функція виводу літери на екран, згідно документації на BIOS, виводить літеру стільки раз, скільки вказано в CX, і він же використовується як лічильник циклу для виводу послідовних літер.
- Тобто, першу літеру виводити стільки раз, скільки літер у стрічці, другу – на один раз менше тощо.
- Однак, візуально функція працює – оскільки кожного разу переводить курсор на правильну позицію. Для перевірки скопіював у окрему програму – справді, спершу заповнює все речення першою літерою, потім, з другої – другою і так до останньої. Результат правильний, тільки це потребує багато більше часу.
- Виглядає4, що покладається на century регістр RTC, вважаючи, що якщо він містить не 20 і не 195, то встановити 19.
- Тому, коли я встановив 1979 рік, він став 2079. :-)
- Потенційна проблема в тому, шо century регістр з’явився в RTC, власне, як частина пошуку рішення проблеми 2000-го року… Враховуючи, що автори “іноземної” плати покладалися на схожий принцип, певне мали підстави довіряти наявності його в RTC.
- Перехоплює переривання 1A та 1C.
- Під час ініціалізації робить щось дивне з alarm – можливо, налаштовує на оновлений час.
- Для 1A реалізовує функції 0-7.
- Також, якщо код операції AH=0xB1, яка відповідає запиту до PCI BIOS, стрибає до захардкодженої адреси 0F000h:0FE6Eh – не видається, що воно всюди працюватиме…
- Колега по хобі каже, що працювало на материнській платі ECS SI5PI AIO (REV. 1.1), обладнаній PCI. Чи то ця адреса справді була поширена – але не гуглиться, чи то тим PCI BIOS на практиці не дуже користувалися? Якщо хтось знає, розкажіть!
- Інша плата теж вважає, що за цією адресою є обробник int 1A.
- Реалізація підтримуваних викликів int 1A, окрім операції AH=04, GET REAL-TIME CLOCK DATE, видається тривіальною – виконує відповідні звертання до RTC і все.
- Для операції AH=04, GET REAL-TIME CLOCK DATE:
- Читає сторіччя і рік з RTC.
- Якщо рік не менший, ніж 0x80 (RTC працює в BCD, тому це ‘‘шістнадцяткове’’ число буквально зображає десяткове), перевіряє сторіччя. Якщо не 0x19 – встановлює відповідний регістр RTC в 19-те.
- Інакше (рік – до 0x80), перевіряє, чи сторіччя – 20-те (і так, з цією платою вилізе проблема 2080 року :=). Якщо так – не робить нічого, інакше – встановлює 20-те і записує в RTC.
- Після чого вичитує вміст RTC та повертає його.
- Також, при вході в обробник 1A, перевіряє, чи обробник 1C не вказує в сегмент 0xF000 – ROM BIOS, і якщо так, відновлює його на свій.
- Як на мене, сумнівне рішення.
- Також, якщо код операції AH=0xB1, яка відповідає запиту до PCI BIOS, стрибає до захардкодженої адреси 0F000h:0FE6Eh – не видається, що воно всюди працюватиме…
- Переривання 1C викликається за кожного виклику int 08 від таймера – за замовчуванням, 18.2 раз за секунду.
- Його обробник, якщо лічильник (BIOS Data Area Daily Timer Counter – кількість тіків int 08 з півночі) дорівнює 0x0F, читає час і дату (лише, якщо час 00:00) – можливо, оновлюючи century регістр в процесі, за описаною вище процедурою.
- Для затримки використовує якісь хитрі танці з PIT – не став розбиратися.
- Містить стрічки:
BIOS-Y2000 ProblemSolver. Version 1.77
,Your BIOS & RTC are ready for New Year!
,Product of Ukraine.
- Проект IDA з дизасемблюваним кодом та моїми анотаціями можна завантажити тут. Оскільки дивився нашвидкоруч і лише для задоволення, там точно є помилки.
Випробування
- Спробував на кількох платах з 286, однією 386, однією з Cyrix 486DX2-66 та однією з Інтелівським 486DX-100. А також, на XT-сумісній.
- Ініціалізується на всіх.
- Однак, на 286-х не виводить текст – тому, спочатку, я вирішив, що воно взагалі не працює.
- Хоча в інструкції пише про призначення для комп’ютерів від 286 і новіших, не має ні команд 286-го, ні перевірки на його наявність, тому на XT запустилося – але, через відсутність RTC, не працює. Також, показала текст.
- На інших повідомлення показує лише частку секунди – витратив багато часу, щоб змогти сфотографувати, тому фото перекособочені.
- Ймовірно, справа у якихось помилках в конфігуруванні PIT для затримок. Перша думка була? що причиною є швидкодія, однак, цю гіпотезу заперечує видиме повідомлення на XT.
- Намагаючись зрозуміти, чому “не працює” на 286-х, пошукав дебагером сигнатуру і виявив, що вміст модуля ROM є, але відображається не, як обіцяно, на 0xD8000, 0xD0000, 0xC8000, а на 0xDA000, 0xD2000, 0xCA0006, відповідно – на 0x2000 байт, 8 Кб, зсунуто.
- Проаналізував схему – вона двошарова і зовсім проста, там жодної магії немає – мінімальний інтерфейс до ISA, тому, підозрюю, проблема в тому, що код на мікросхему ROM зашитий зі зміщенням 0x2000.
- Або я помилково прочитав схему і десь там помилка – переплутано адресні біти.
- На 486-х, якщо використовувати разом з PicoMEM, довелося їх розносити на протилежні краї – D8000 і C8000, якщо використати D0000 – не ініціалізувалися обидві. Може це якось пов’язане із тим, що мікросхема ROM – на 32Кб… Однак, на 286-х такого не було.
![]() |
---|
Впійманий вивід плати. |
![]() |
Відповідний BIOS в пам’яті, коли перемичка на D800. |
Аналіз коду “конкурента”
Колеги по хобі підкинули ще одну плату – DRBIOS2000 Y2K BIOS Update Card.
![]() |
---|
Коробочка плати, фото з архіву. |
Код справляє враження більш акуратного і більш консервативного, однак, ідейно покладається на такі ж рішення.
- При вході тестує на 286, і відмовляється працювати на старіших процесорах.
- Також, тестує багато чого іншого:
- Чи вектор int 1A в сегменті системного BIOS (0xF000).
- Перевіряє сигнатуру машини в BIOS, за адресою 0xF000:0FFFE7.
- Також, перевіряє чи зміщення обробника якесь конкретне (0xFE6E), а якщо ні – чи там стрибок з опкодом 0xE9, ціль якого збігається з іншим розрахованим значенням з магічними константами.
- Має два обробника int 1A – для AT і для PS/2. Останній я не аналізував.
- В обробнику для AT підтримує команди від 0 до 7, як і наша плата. Але всі інші повністю ігнорує.
- Реалізація, окрім функції 4 – тривіальна.
- У ній дивиться, якщо сторіччя не 19 – не робить нічого.
- Якщо ж ні, то виконує таку арифметику:
((century/16)+1)*16
. Для 1Ah вона дає 20h – 20-те сторіччя в BCD. - Ідея в тому, що, якщо до 19h додати 1, отримується 1A і воно транслюється в 20h – BCD зображення 20-го сторіччя.
- Не перехоплює int 1C і не намагається відновлювати свій вектор переривань – воно, певне, і на краще.
- Містить такі текстові рядки: “Digital Research Technologies, Inc.”, “DRBIOS 2000 v1.10”, “Copyright 1991-1998 Micro Firmware, Inc.”, “Portions Copyright 1985-1998 Phoenix Technologies Ltd.” – остання вказує, звідки могла бути взята реалізація більшості реалізованих функцій int 1A.
- Код виводу тексту теж видається дещо переускладненим, але явних багів не бачу.
- Додає більше затримок під час роботи з RTC, ретельніше перевіряє, чи він не на стадії оновлення.
- Проект IDA з дизасемблюваним кодом та моїми анотаціями можна завантажити тут. Оскільки дивився нашвидкоруч і лише для задоволення, там точно є помилки.
Не маючи фізично, (і не маючи натхнення повозитися з “донором” – умовною Ethernet платою з слотом для ROM), не випробовував. В цілому, хоча без системних випробувань сказати впевнено неможливо, видається, що українська плата могла бути більш функціональною і менш капризною – хай може й небезпечнішою, через меншу перевірку, на якій машині вона працює.
Фото та інструкція
Коробочка
Плата прийшла до мене в акуратній коробочці з доволі гарно зробленою інструкцією – наводжу фото далі. На жаль, вони доволі неякісні – але, боюся, інакше я цей пост ніколи не дописав би.
![]() |
---|
Передня сторона коробочки. |
![]() |
Задня сторона. |
![]() |
![]() |
Боки. |
![]() |
Вигляд зсередини. |
Інструкція
![]() |
---|
![]() |
![]() |
![]() |
![]() |
![]() |
Посилання
- Переривання 0x1A.
- PS2 and PC BIOS Interface Technical Reference (Apr. 87), зокрема – стор. 4-12 (формат модуля) та 3-3 (BIOS Data Area).
- CMOS, зокрема розділ Century Register.
- Bochs’s CMOS map – лише список.
- CMOS RTC - Real Time Clock and Memory (ports 70h & 71h) – містить формати регістрів, які мені не траплялися в інших місцях.
- Intel 8253 – PIT для IBM PC.
- BDA - BIOS Data Area - PC Memory Map
- Year 2000 bug fix option ROM (BIOS Y-2000 PS) – фото плати і образ BIOS від людини, що допомогла мені отримати плату.
- DRBIOS2000 Y2K BIOS Update Card EEPROM DUMP – інша аналогічна плата.
Виноски
-
Завдяки ретрокомп’ютерній спільноті. ↩
-
Згадуємо тут зразу нещодавній коронавірус. ↩
-
Дві мікросхеми 74-ї серії виконують допоміжну роль – одна допомагає декодувати адресу, інша – буферизує вивід. ↩
-
Якщо я все правильно зрозумів. ↩
-
RTC працює з BCD-числами – потрібно про це пам’ятати, аналізуючи. ↩
-
Вказую адреси в байтах, зазвичай тут (як ось на самій платі) вказують ‘‘сегмент’’ 8086-го – без останньої цифри. ↩
-
Зізнаюся, не зважаючи на купу прочитаної літератури, я чи то не знав, чи то зовсім забув про цю сигнатуру. Детальніше див. тут, стор. 71 та тут. Підсумовуючи ці джерела, 0xFB – XT v2, v3 (XT v1 – FE?), 0xFC – XT286, 0xFC – AT, FF – PC. Також, з BIOS-ів PCem, 0xFC чи 0xF8 – PS/1, 0xFC – PS/2 M30, 0xF8 – PS/2 M70, 0xFD – PCJr. ↩