Фольклор программистов и инженеров (часть 2)

в 7:00, , рубрики: Блог компании NIX, инженерные системы, истории из жизни, Программирование, фольклор, Читальный зал
Фольклор программистов и инженеров (часть 2) - 1

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

Больше магии

Несколько лет назад я шарился по шкафам, в которых располагался компьютер PDP-10, принадлежавший лаборатории искусственного интеллекта в MIT. И заметил маленький переключатель, приклеенный к раме одного из шкафов. Было видно, что это самоделка, добавленная одним из лабораторных умельцев (никто не знал, кем именно).

Вы не будете трогать неизвестный переключатель на компьютере, не зная, что он делает, потому что вы можете сломать компьютер. Переключатель был подписан совершенно невразумительно. У него было два положения, и на металлическом корпусе карандашом были накарябаны слова «магия» и «больше магии». Переключатель стоял в положении «больше магии». Я позвал одного из техников, чтобы он взглянул. Он прежде не видел такого. При внимательном рассмотрении выяснилось, что к переключателю идёт всего лишь один провод! Другой конец провода исчезал в мешанине кабелей внутри компьютера, но природа электричества подсказывает, что переключатель не будет ничего делать, пока к нему не подключишь два провода.

Было очевидно, что это чья-то глупая шутка. Убедившись, что переключатель ничего не делает, мы переключили его. Компьютер немедленно отрубился.

Представьте себе наше изумление. Мы списали произошедшее на совпадение, но всё равно вернули кнопку в положение «больше магии», прежде чем запустить компьютер.

Год спустя я рассказал эту историю другому технику, Дэвиду Муну, насколько я помню. Он усомнился в моей адекватности, или заподозрил в вере в сверхъестественную природу того переключателя, или решил, что я дурачу его липовой историей. Чтобы доказать свои слова, я показал ему этот переключатель, всё ещё приклеенный к раме и с единственным проводом, по-прежнему в положении «больше магии». Мы внимательно изучили переключатель и провод и обнаружили, что тот был подключён к заземлению. Это выглядело вдвойне бессмысленным: переключатель был не только электрически неработоспособен, но ещё и подключён к месту, которое ни на что не влияет. Мы перевели его в другое положение.

Компьютер сразу вырубился.

Мы обратились к находившемуся поблизости Ричарду Гринблатту, который давно работал техником в MIT. Он тоже никогда не видел переключатель. Осмотрел его, пришёл к выводу, что переключатель бесполезен, достал кусачки и отрезал провод. Затем мы включили компьютер и тот спокойно начал работать.

Мы до сих пор не знаем, как этот выключатель вырубал компьютер. Есть гипотеза, что поблизости от контакта заземления возникало небольшое короткое замыкание, а перевод положений переключателя менял электрическую ёмкость настолько, чтобы цепь прерывалась, когда через неё проходили импульсы длительностью в миллионные доли секунды. Но точно мы уже не узнаем. Можно лишь сказать, что переключатель был волшебным.

Он всё ещё лежит у меня в подвале. Наверное, это глупо, но обычно я храню его в положении «больше магии».

В 1994-м было предложено ещё одно объяснение этой истории. Обратите внимание, что корпус переключателя был металлическим. Предположим, что контакт без второго провода был подключён к корпусу (обычно корпус заземляют, но бывают исключения). Корпус переключателя был соединён с корпусом компьютера, который, вероятно, был заземлён. Тогда в цепи заземления внутри машины может быть другой потенциал, чем в цепи заземления корпуса, а смена положения переключателя приводила к падению или всплеску напряжения, и машина перезагружалась. Вероятно, этот эффект обнаружил кто-то, кто знал о разнице потенциалов, и решил сделать такой шуточный переключатель.

OpenOffice не печатает по вторникам

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

В отчётах о баге некоторые изначально жаловались на то, что это должен быть баг OpenOffice, потому что из всех других приложений печаталось без проблем. Другие отмечали, что проблема возникает и пропадает. Один пользователь нашёл решение: удалить OpenOffice и очистить от него систему, затем установить заново (любая простая задача на Ubuntu). Пользователь сообщил во вторник, что его проблема с печатью решилась.

Через две недели он написал (во вторник), что его решение всё-таки не сработало. Примерно через четыре месяца жена Ubuntu-хакера пожаловалась, что OpenOffice не печатает по вторникам. Представляю себе эту ситуацию:

Жена: Стив, принтер не работает по вторникам.

Стив: Это выходной день у принтера, конечно он не печатает по вторникам.

Жена: Я серьёзно! Я не могу печатать из OpenOffice по вторникам.

Стив: (недоверчиво) Ладно, покажи.

Жена: Я не могу тебе показать.

Стив: (закатив глаза) Почему?

Жена: Сегодня среда!

Стив: (кивает, медленно произносит) Верно.

Проблему удалось отследить до программы под названием file. Эта *NIX-утилита с помощью шаблонов определяет типы файлов. Например, если файл начинается с %!, а потом идёт PS-Adobe-, тогда это PostScript. Похоже, OpenOffice пишет данные в такой файл. Во вторник он берёт форму %%CreationDate: (Tue MMM D hh:mm:...). Ошибка в шаблоне для файлов Erlang JAM означала, что Tue в PostScript-файле распознавалось как файл Erlang JAM, и поэтому, предположительно, он не отправлялся на печать.

Шаблон для файла Erlang JAM выглядит так:

4 string Tue Jan 22 14:32:44 MET 1991 Erlang JAM file - version 4.2

А должен выглядеть так:

4 string Tue Jan 22 14:32:44 MET 1991 Erlang JAM file - version 4.2

Учитывая множество типов файлов, которые пытается распознать эта программа (больше 1600), ошибки в шаблонах не удивляют. Но к частым ложноположительным срабатываниям приводит ещё и порядок сопоставления. В данном случае тип Erlang JAM сопоставлялся до типа PostScript.

Пакеты смерти

Я начал называть их так потому, что они были именно пакетами смерти.

Компания Star2Star сотрудничала с производителем аппаратного обеспечения, который создал две последние версии нашей локальной клиентской системы.

Около года назад мы выпустили обновление для этого оборудования. Всё начиналось довольно просто, в соответствии с обычным законом Мура. Больше, лучше, быстрее, дешевле. Новое оборудование было 64-битным, имело в 8 раз больше памяти, вмещало больше дисков и имело четыре гигабитных Ethernet-порта Intel (мой любимый производитель Ethernet-контроллеров). У нас было (и есть) много идей, как использовать эти порты. В общем, железка была захватывающей.

Новинка пулей просвистела через тесты производительности и функциональности. И скорость высокая, и надёжность. Идеально. Затем мы не спеша развернули оборудование на нескольких тестовых площадках. Конечно же, начали возникать проблемы.

Быстрый поиск в Google подсказывает, что у Ethernet-контроллера Intel 82574L было как минимум несколько проблем. В частности, проблемы с EEPROM, баги в ASPM, выкрутасы с MSI-X и т.д. Мы несколько месяцев решали каждую из них. И думали, что закончили.

Но нет. Стало только хуже.

Я думал, что разработал и развернул идеальный программный образ (и BIOS). Однако реальность была иной. Модули продолжали сбоить. Иногда они восстанавливались после перезагрузки, иногда нет. Однако после восстановления модуля его нужно было протестировать.

Надо же. Ситуация становилась странной.

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

Остановлюсь на этом подробнее. Когда я говорю, что Ethernet-контроллер «мог упасть», это означает, что он МОГ УПАСТЬ. Система и Ethernet-интерфейс выглядели нормально, а после передачи случайного количества трафика интерфейс мог сообщить об аппаратной ошибке (потеря связи с PHY) и терял соединение. Светодиоды на свиче и интерфейсе буквальным образом гасли. Контроллер был мёртв.

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

Во время отладки с помощью этого очень терпеливого реселлера я заметил прекращение получения пакетов при падении интерфейса. В конце концов я определил закономерность: последним пакетом от интерфейса всегда был 100 Trying provisional response, и у него всегда была определённая длина. Также я отследил этот (Asterisk) ответ до INVITE, разработанного производителем телефонов.

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

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

Поверьте, на это ушло много времени. Я знаю, как работает стэк OSI. Я знаю, как сегментировано ПО. Я знаю, что содержимое SIP-пакетов не должно влиять на Ethernet-адаптер. Это всё ерунда какая-то.

Наконец нам удалось изолировать проблему пакетов на отрезке между их приходом в наше устройство и на порте зеркалирования в свиче. Похоже, что мы получали INVITE, а не отправленный 100 Trying! Порт зеркалирования не получал от сети 100 Trying.

Нужно было разобраться с этим INVITE. Может быть, проблема была связана с обработкой этого пакета демоном пользовательского пространства (userspace daemon)? Может быть, проблема была в передаче 100 Trying? Один из коллег предложил закрыть SIP-приложение и посмотреть, сохранится ли проблема. Без этого приложения пакеты 100 Trying не передавались.

Нужно было как-то улучшить передачу проблемных пакетов. Мы изолировали передаваемый с телефона пакет INVITE и проиграть его с помощью tcpreplay. Это сработало. Впервые за несколько месяцев мы смогли по команде уронить порты с помощью одного пакета. Это был значительный прогресс, и пора было идти домой, то есть повторить тестовый стенд в домашней лаборатории!

Прежде, чем я продолжу рассказ, хочу рассказать о прекрасном open source-приложении, которое я нашёл. Ostinato превращает вас в пакетного ниндзя. Его возможности буквально безграничны. Без этого приложения я не смог бы продвинуться дальше.

Вооружившись этим швейцарским ножом для пакетов, я начал экспериментировать. Меня поразило то, что я обнаружил.

Всё начиналось со странной SIP/SDP-причуды. Взгляните на этот SDP:

v=0
o=- 20047 20047 IN IP4 10.41.22.248
s=SDP data
c=IN IP4 10.41.22.248
t=0 0
m=audio 11786 RTP/AVP 18 0 18 9 9 101
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=rtpmap:0 PCMU/8000
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=rtpmap:9 G722/8000
a=rtpmap:9 G722/8000
a=fmtp:101 0-15
a=rtpmap:101 telephone-event/8000
a=ptime:20
a=sendrecv

Да, всё верно. Предложение о передаче звука дублируется. Это проблема, но повторюсь, какое отношение имеет к этому Ethernet-контроллер?! Ну, не считая того, что больше ничто другое не увеличивает размер Ethernet-фрейма… Но подождите, в передаваемых пакетах было много успешных Ethernet-фреймов. Какие-то из них были меньше, какие-то больше. С ними проблем не было. Пришлось копать дальше. После нескольких приёмов кунг-фу с помощью Ostinato и кучи переподключений электричества я смог выявить проблемную взаимосвязь (с проблемным фреймом). Внимание: мы будем рассматривать шестнадцатеричные значения.

Падение интерфейса инициировалось определённым значением байта с определённым смещением: шестнадцатеричное значение 32 (в ASCII это 2) с 0x47f. Угадайте, откуда взялось 2.

a=ptime:20

Все наши SDP были идентичны (включая ptime). Все исходные и конечные URI были идентичны. Единственным отличием были ID вызовов, тэги и ветки (branches). Проблемные пакеты имели такое сочетание ID вызова, тэгов и веток, при котором в ptime получалось значение 2 со смещением 0x47f.

Вот так! С правильными ID, тэгами и ветками (или любым случайным мусором) «хороший пакет» мог превратиться в «пакет-убийцу», как только строка ptime заканчивалась на определённом адресе. Это было очень странно.

При генерировании пакетов я поэкспериментировал с разными шестнадцатеричными значениями. Ситуация оказалась ещё запутанней. Выяснилось, что поведение контроллера целиком зависело от этого конкретного значения, находящегося по указанном адресу в первом полученном пакете. Картина была такая:

Байт 0x47f = 31 HEX (1 ASCII) - Никакого эффекта
Байт 0x47f = 32 HEX (2 ASCII) - Интерфейс падает
Байт 0x47f = 33 HEX (3 ASCII) - Интерфейс падает
Байт 0x47f = 34 HEX (4 ASCII) - Трансформирование (inoculation) интерфейса

Когда я говорил «не влияет», я имел в виду не только не убивает интерфейс, но и не трансформирует (более или менее). А когда я говорю, что «интерфейс падает», ну, помните моё описание? Интерфейс умирает. Напрочь.

После новых тестов я выяснил, что проблема сохраняется с каждой версией Linux, которую мне удалось найти, с FreeBSD, и даже при включении машины без загрузочного носителя! Дело было в оборудовании, а не в ОС. Ого.

Более того, с помощью Ostinato я смог создать разные версии пакета-убийцы: HTTP POST, ICMP echo-запрос и другие. Почти всё, что я хотел. С помощью модифицированного HTTP-сервера, который генерировал данные в байтовых значениях (на основе заголовков, хоста и т.д.), можно было легко создать 200-й HTTP-запрос, чтобы тот содержал пакет смерти и убивал клиентские машины за файрволом!

Я уже объяснял, насколько странной была вся ситуация. Но самым странным было трансформирование. Оказалось, что если первый полученный пакет содержал любое значение (из опробованных мной), за исключением 1, 2 или 3, тогда интерфейс становился неуязвимым для любых пакетов смерти (содержавших значения 2 или 3). Более того, валидные атрибуты ptime были кратными 10: 10, 20, 30, 40. В зависимости от сочетания ID вызова, тэга, ветки, IP, URI и прочего (с этим забагованным SDP), эти валидные атрибуты ptime выстраивались в идеальную последовательность. Каковы были шансы на это?!

Внезапно стало ясно, почему проблема возникала спорадически. Удивительно, что мне удалось это выяснить. Я 15 лет работаю с сетями и не встречал ничего подобного. И сомневаюсь, что встречу опять. Надеюсь…

Я связался с двумя инженерами в Intel и отправил им демо, чтобы они могли воспроизвести проблему. Поэкспериментировав пару недель, они выяснили, что проблема была связана с EEPROM в контроллерах 82574L. Они прислали мне новую EEPROM и инструмент для записи. К сожалению, мы не могли его распространять, к тому же требовалось выгружать и перезагружать модуль ядра e1000e, поэтому инструмент не подходил для нашего окружения. К счастью (немного зная схему EEPROM) я смог написать bash-скрипт, а затем с помощью магии ethtool сохранил «исправленные» значения и прописал их в системах, где проявлялся баг. Теперь мы могли выявлять проблемные аппараты. Мы связались с нашим вендором, чтобы тот применил исправление ко всем устройствам, прежде чем отгружать нам. Неизвестно только, сколько таких Ethernet-контроллеров Intel уже было продано.

На одну паллету больше

В 2005-м у меня на работе была необъяснимая проблема. Через день после незапланированного закрытия (из-за урагана) я начал получать звонки от пользователей, которые жаловались на таймауты при подключении к базе данных. Поскольку у нас была очень простая сеть на 32 ноды и с практически неиспользуемой пропускной способностью, меня напугало, что можно было 15-20 минут пинговать сервер с БД, а потом примерно две минуты получать «request timed out». Я запустил на этом сервере мониторинг производительности и прочие инструменты, и начал пинговать его из разных мест. За исключением сервера, остальные машины всё время могли общаться с другими участниками сети. Я искал сбойный свич или соединение, но не мог найти объяснение случайным и периодическим сбоям.

Я попросил коллегу понаблюдать за светодиодами на свиче, расположенном на складе, пока я занимался трассировкой и переподключал разные устройства. Прошло 45-50 минут, коллега сообщал мне по рации «этот выключен, тот поднялся». Я спросил, не заметил ли он какой-то закономерности.

— Да… заметил. Но ты подумаешь, что я чокнутый. Каждый раз, когда погрузчик вывозит паллету из отгрузочного зала, на сервере возникает таймаут в две секунды.

— ЧТО???

— Ага. И сервер восстанавливается, когда погрузчик начинает отгружать новый заказ.

Я прибежал посмотреть на погрузчик и был уверен, что он отмечает успешное завершение заказа включением какого-нибудь гигантского магнетрона. Несомненно, электромагнитные волны от конденсатора приводят к разрыву пространственно-временного континуума и временно прерывают работу серверной NIC-карты, находящейся в другой комнате в 50 метрах. Нет. Погрузчик просто ставил на паллету более крупные коробки, сверху — коробки поменьше, при этом сканируя каждую коробку беспроводным сканером штрих-кодов. Ага! Наверное, это сканер обращается к серверу базы данных, что приводит к сбою других запросов. Неа. Я проверил и выяснил, что сканер тут ни при чём. Беспроводной маршрутизатор и его ИБП в отгрузочном зале были сконфигурированы правильно и функционировали нормально. Причина была в чём-то другом, ведь до закрытия из-за урагана всё прекрасно работало.

Как только начался следующий таймаут, я прибежал в отгрузочный зал и наблюдал, как грузчик наполняет следующую паллету. Едва он поставил четыре большие коробки шампуня на пустой поддон, сервер снова стал недоступен! Я не верил в абсурдность происходящего и ещё пять минут снимал и ставил коробки шампуня, с тем же результатом. Я уже готов был пасть на колени и молить о милосердии Бога Интранета, когда заметил, маршрутизатор в отгрузочном зале висит примерно на 30 см ниже уровня коробок, стоявших на паллете. Есть зацепка!

Когда на паллету ставили большие коробки, у беспроводного маршрутизатора пропадала прямая видимость с внешним складом. Через десять минут я решил проблему. Вот что произошло. Во время урагана был сбой в электросети, из-за которого сбросилось единственное устройство, не подключённое к ИБП — тестовый беспроводной маршрутизатор в моём офисе. Настройки по умолчанию почему-то превратили его в репитер для единственного другого беспроводного маршрутизатора, висевшего в отгрузочном зале. Оба устройства могли общаться друг с другом только тогда, когда между ними не было паллеты, но даже в этом случае сигнал не был слишком сильным. Когда маршрутизаторы общались, они создавали петлю в моей маленькой сети, и тогда все остальные пакеты к серверу БД терялись. У сервера был свой свич от основного маршрутизатора, поэтому как узел сети он находился гораздо дальше. Большинство других компьютеров висело на том же 16-портовом свиче, так что я без проблем пинговался между ними.

Я за одну секунду решил проблему, над которой мучился четыре часа: отключил питание у тестового маршрутизатора. Больше таймаутов на сервере не было.

Как в фильме Tron, только на компьютере Apple IIgs

В детстве одним из моих любимых фильмов был Tron, снятый в начале 1980-х. В нём рассказывалось о программисте, который «оцифровался» и был поглощён компьютерным миром, населённым персонифицированными программами. Протагонист присоединился к группе сопротивления в попытке свергнуть угнетателя Master Control Program (MCP), взбунтовавшуюся программу, которая эволюционировала, обрела жажду власти и попыталась захватить компьютерную систему Пентагона.

В одной из самых впечатляющих сцен программы-персонажи устраивают гонки на светоциклах — двухколёсных машинах, похожих на мотоциклы, оставлявших позади себя стены. Один из протагонистов заставил вражеский пепелац врезаться в стену на арене, проделав сквозную дыру. Герои расправились с противниками и сбежали через дыру на свободу — первый шаг на пути к свержению МСР.

Когда я смотрел фильм, даже не представлял, что годы спустя нечаянно воссоздам на компьютере Apple IIgs мир Tron, взбунтовавшихся программ и всего прочего.

Вот как это произошло. Когда я начал учиться программированию, я решил создать игру со светоциклами из Tron. Вместе со своим другом Марко я на Apple IIgs писал программу на ORCA/Pascal и ассемблере 65816. Во время игры экран закрашивался чёрным цветом с белой окантовкой. Каждая линия представляет одного из игроков. Мы отображали игровые баллы в строке в нижней части экрана. Графически это была не самая продвинутая программа, но зато простая и весёлая. Выглядела она так:

Фольклор программистов и инженеров (часть 2) - 2

Игра поддерживала до четырёх игроков, если бы те могли усесться перед одной клавиатурой. Было неудобно, но работало. Нам редко удавалось собрать нужное количество людей, чтобы задействовать все четыре светоцикла, поэтому Марко добавил управляемых компьютеров игроков, которые могли составить разумную конкуренцию.

Гонка вооружений

Игра уже была очень забавной, но нам захотелось экспериментов. Мы добавили ракеты, чтобы у игроков был шанс спастись от неминуемой аварии. Как позднее описывал Марко:

У людей и ИИ было по три ракеты, которые можно было использовать в течение игры. Когда ракета попадала в стену, возникал «взрыв», фон которого закрашивался чёрным, тем самым удалялись секции следа, оставленного предыдущими светоциклами.

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

Побег

Как и все необычные и причудливые события, это было ещё и неожиданное.

Однажды, когда мы с Марко играли против двух компьютерных игроков, мы загнали один из ИИ-циклов в ловушку между его собственной стеной и нижней границей экрана. Предвидя неминуемую аварию, он выстрелил ракетой, как и всегда до этого. Но в этот раз вместо стены он выстрелил в границу экрана, которая выглядела как след одного из светоциклов. Ракета попала в границу, оставила дыру размером со светоцикл, и компьютер незамедлительно покинул через неё игровое поле. Мы в замешательстве уставились на светоцикл, который проехал через строку с баллами. Он легко избегнул столкновения с символами, а затем вообще уехал с экрана.

И сразу после этого система упала.

Фольклор программистов и инженеров (часть 2) - 3

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

Что мы сделали, когда обнаружили дефект в нашей программе, который мог методически рушить всю систему? Мы повторили всё вновь. Сначала попробовали сами выбраться за пределы. Затем снова заставили компьютер сбежать. Каждый раз наградой нам были фантастические падения системы. Иногда лампочка дисковода моргала, когда накопитель бесконечно кряхтел. В другие разы экран заполнялся бессмысленными символами, или динамик издавал вопль или низкое гудение. А иногда всё это происходило сразу, и компьютер оказывался в состоянии полного раздрая.

Почему так происходило? Чтобы разобраться в этом, давайте рассмотрим архитектуру компьютера Apple IIgs.

(Не)защищённая память

Операционная система Apple IIgs не имела защищённой памяти, появившейся в более поздних ОС, когда области памяти назначались программа и защищались от доступа извне. Поэтому программа под Apple IIgs могла читать и писать всё, что угодно (за исключением ПЗУ). Для доступа к устройствам вроде дисковода IIgs использовал операции ввода-вывода с привязкой к памяти, поэтому можно было активировать дисковод, прочитав из определённой области памяти. Такая архитектура позволяла графическим программам читать и писать прямо в память экрана.

В игре использовался один из графических режимов Apple IIgs — Super Hi-Res: восхитительное 320x200 пикселей с палитрой из 16 цветов. Для выбора палитры программист задавал 16 записей (пронумерованных от 0 до 15 или от $0 до F в шестнадцатеричном формате) для 12-битных значений цветов. Для рисования на экране можно было читать и писать цвета прямо в видеопамять.

Алгоритм определения сбоя

Мы воспользовались этой особенностью и реализовали определитель сбоя, считывая прямо из видеопамяти. Игра вычисляла для каждого светоцикла его следующую позицию на основе текущего направления, и считывала этот пиксель из видеопамяти. Если позиция была пустой, то есть представлена чёрным пикселем (запись в палитре $0), тогда игра продолжалась. Но если позиция была занята, игрок врезался в светоцикл или белую рамку экрана (запись в палитре 15 или $F). Пример:

Фольклор программистов и инженеров (часть 2) - 4

Здесь показан верхний левый угол экрана. Цвет $F обозначает белую рамку, а цветовая запись $1 обозначает зелёный светоцикл игрока. Он движется влево, как показано стрелкой, то есть следующий пиксель пуст, его цвет $0. Если игрок продолжит двигаться в этом направлении более одного хода, он может столкнуться со стеной (цвет $F) и разбиться.

Выход за пределы

Алгоритм определения следующего пикселя с помощью ассемблерной математики быстро вычислял адрес в памяти одного пикселя выше, ниже, слева или справа от текущего пикселя. Поскольку любой пиксель на экране представлял собой адрес в памяти, алгоритм просто вычислял новый адрес, который нужно прочитать. И когда светоцикл покидал экран, алгоритм определял место в системной памяти для проверки столкновения со стеной. Это означало, что светоцикл теперь путешествовал по системной памяти, бессмысленно включая биты и «врезаясь» в память.

Запись в случайные ячейки системной памяти нельзя назвать мудрым архитектурным решением. Неудивительно, что игра из-за этого падала. Игрок-человек не будет ездить вслепую и обычно сразу врезается, что ограничивает масштабы проблем в системе. А у ИИ такой слабости нет. Компьютер мгновенно сканирует позиции вокруг себя, чтобы определить, врежется ли он в стену, и меняет своё направление. То есть, по мнению компьютера системная память ничем не отличалась от экранной памяти. Как описывал Марко:

Мы могли только гадать, что делает алгоритм, покидая экранную память. Очевидно, что он искал способ продолжить движение в направлении, у которого были значения 0. Если бы алгоритм попал в тупик и врезался в «стену» из чисел, он умер бы. Но иногда в таких ситуациях он оставался «живым», пока в какой-то момент не перезаписывал своим следом какой-нибудь исполняющийся код, или пока не обращался к памяти, привязанной к устройству — и тогда система падала. Но случалось это не сразу же после выхода за пределы экрана, ИИ какое-то время ещё бегал, пока не рушил систему.

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

Сегодня такое трудно повторить, поскольку операционные системы обзавелись защищённой памятью. Но я всё ещё гадаю, существуют ли программы как в Tron, которые пытаются выбраться из своих «защищённых пространств» в попытке помешать мятежному ИИ-коду захватить Пентагон.

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

Сесть, чтобы залогиниться

Каждый программист знает, что отлаживать тяжело. Хотя у отличных отладчиков работа выглядит обманчиво простой. Обезумевшие программисты описывают баг, который они часами вылавливают, мастер задаёт несколько вопросов, и через несколько минут программисты видят перед собой сбойный код. Отладчик-эксперт не забывает, что всегда есть логическое объяснение, вне зависимости от того, насколько таинственно ведёт себя система на первый взгляд.

Такое отношение проиллюстрировано анекдотом, появившимся в исследовательском центре IBM Yorktown Heights. Программист недавно установил новую рабочую станцию. Всё было прекрасно, когда он сидел перед компьютером, но он не мог залогиниться, пока стоял. Это поведение воспроизводилось всегда: сидя программист залогинивался всегда, стоя — не смог ни разу.

Многие из нас просто сидели и удивлялись. Откуда компьютер мог знать, стоят ли перед ним или сидят? Однако хорошие отладчики знают, что должна быть причина. Первое, что приходит в голову — электричество. Надорванный провод под ковром, или статический заряд? Но проблемы, связанные с электричеством, редко воспроизводятся в 100 % случаев. Один из коллег наконец задал правильный вопрос: каким образом программист логинился сидя и стоя? Попробуйте сами.

Причина была в клавиатуре: две кнопки поменяны местами. Когда программист сидел, он набирал вслепую, и проблема оставалась незамеченной. А когда он стоял, это его сбивало с толку, он выискивал кнопки и нажимал их. Вооружившись этой подсказкой и отвёрткой, отладчик-эксперт поменял кнопки местами и всё наладилось.

Банковская система, развёрнутая в Чикаго, много месяцев работала нормально. Но неожиданно завершила работу, когда её впервые применили для обработки международных данных. Программисты днями ковыряли код, но не могли найти ни одной команды, которая приводила к завершению программы. Когда они внимательнее понаблюдали за её поведением, то обнаружили, что программа завершалась, если в неё вводили данные по Эквадору. Анализ показал, что когда пользователь печатал название столицы (Quito), программа интерпретировала это как команду завершения!

Однажды Боб Мартин встретил систему, «работавшую один раз дважды». Она корректно обрабатывала первую транзакцию, а во всех последующих транзакциях возникали небольшие проблемы. Когда систему перезагрузили, она опять корректно обработала первую транзакцию и засбоила на всех последующих. Когда Боб охарактеризовал это поведение как «работавшая один раз дважды», разработчики немедленно поняли, что нужно было искать переменную, которая корректно инициализировалась при загрузке программы, но не сбрасывалась после первой транзакции. Во всех случаях правильные вопросы позволяли мудрым программистам в кратчайшие сроки выявить неприятные ошибки: «Что ты делал иначе, когда стоял и сидел? Покажи, как ты логинишься в обоих случаях», «Что именно ты вводил перед завершением программы?» «Программа работала правильно, прежде чем начались сбои? Сколько раз?»

Рик Лемонс сказал, что лучший урок, связанный с отладкой, он получил, когда наблюдал представление фокусника. Тот проделал пяток невозможных трюков, и Лемонс почувствовал, что верит в это. Затем он напомнил себе, что невозможное не является возможным, и проверил каждый трюк, чтобы доказать это очевидное несоответствие. Лемонс начал с того, что было непоколебимой истиной — с законов физики, и исходя из них он начал искать простые объяснения каждому трюку. Такое отношение делает Лемонса одним из лучших отладчиков, что я встречал.

На мой взгляд, лучшая книга по отладке — это The Medical Detectives, написанная Berton Roueche и опубликованная Penguin в 1991-м. Герои книги отлаживают сложные системы, от умеренно больного человека до очень больных городов. Применяемые там методы решения проблем можно напрямую использовать в отладке компьютерных систем. Эти реальные истории завораживают не меньше, чем любая выдумка.

Случай с 500-мильным email

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

Несколько лет назад я обслуживал систему электронной почты в кампусе. Мне позвонил руководитель департамента статистики.

— У нас проблема с отправкой писем.

— Какая проблема?

— Мы не можем отправлять письма дальше, чем на 500 миль.

Я подавился кофе.

— Не понял.

— Мы не можем отправлять из департамента письма дальше, чем на 500 миль. На самом деле, чуть дальше. Примерно 520 миль. Но это предел.

— Хм… Вообще-то электронная почта работает не так, — ответил я, пытаясь совладать с паникой в голосе. Нельзя проявлять панику в разговоре с руководителем департамента, даже такого, как департамент статистики. — Почему вы решили, что не можете отправлять письма дальше, чем на 500 миль?

— Я не решил, — веско ответил он. — Видите ли, когда мы заметили происходящее, несколько дней назад…

— Вы ждали несколько ДНЕЙ? — оборвал его я дрогнувшим голосом. — И вы не могли отправлять письма всё это время?

— Мы могли отправлять. Просто не дальше…

— Пятисот миль, да, — закончил за него я. — Понятно. Но почему вы раньше не позвонили?

— Ну, до этого момента у нас не было достаточно данных, чтобы удостовериться в происходящем.

Точно, это же руководитель статистики.

— В любом случае, я попросил одного из геостатистиков поработать с этим…

— Геостатистиков…

— Да, и она составила карту, показывающую радиус, внутри которого мы можем отправлять письма, чуть больше 500 миль. В этой зоне есть несколько мест, в которые наши письма не приходят совсем или периодически, но за пределы радиуса мы вообще не можем ничего отправить.

— Понятно, — сказал я и опустил голову на руки. — Когда это началось? Вы сказали, что несколько дней назад, но мы ничего не меняли в ваших системах.

— Приходил консультант, пропатчил и перезагрузил наш сервер. Но это я его вызывал, и он сказал, что не трогал почтовую систему.

— Ладно, давайте я посмотрю и перезвоню вам, — ответил я, с трудом веря, что участвую в подобном. Сегодня было не первое апреля. Я попытался вспомнить, не задолжал ли мне кто-нибудь розыгрыш.

Я залогинился на сервер их департамента и отправил несколько проверочных писем. Это происходило в Исследовательском Треугольнике Северной Каролины, и письмо в мой ящик пришло без проблем. Как и письма, отправленные в Ричмонд, Атланту и Вашингтон. Пришло и письмо, отправленное в Принстон (400 миль).

Но затем я отправил письмо в Мемфис (600 миль). Оно не пришло. В Бостон, не пришло. В Детройт, не пришло. Я достал адресную книгу и начал рассылать по ней письма. В Нью-Йорк (420 миль) пришло, а в Провиденс (580 миль) не пришло.

Я начал сомневаться в своём рассудке. Написал другу в Северной Каролине, провайдер которого был в Сиэтле. К счастью, письмо не пришло. Если бы проблема была связана с местоположением получателей, а не их почтовых серверов, наверное, я разрыдался бы.

Выяснив, что проблема действительно существует (невероятно) и воспроизводима, я начал анализировать файл sendmail.cf. Он выглядел нормально. Привычно. Я сравнил его с sendmail.cf в моей домашней директории. Разницы не было — это был файл, который я написал. И я был уверен, что не включал опцию FAIL_MAIL_OVER_500_MILES. В растерянности я с помощью telnet прозвонил SMTP-порт. Сервер радостно ответил баннером Sendmail из SunOS.

Погодите… баннер Sendmail из SunOS? В то время Sun ещё поставляла Sendmail 5 со своей операционной системой, хотя Sendmail 8 была уже вполне допилена. Поскольку я был хорошим сисадмином, я ввёл Sendmail 8 в качестве стандарта. Кроме того, поскольку я был хорошим сисадмином, я написал sendmail.cf, в котором использовались классные длинные самодокументируемые опции и имена переменных, доступные в Sendmail 8, а не загадочные коды из знаков препинания, применявшиеся в Sendmail 5.

Всё встало на свои места, и я опять подавился своим уже остывшим кофе. Похоже, когда консультант «пропатчил сервер», он обновил версию SunOS, с которой накатил более старую версию Sendmail. К счастью, файл sendmail.cf сохранился, но теперь он не подходил.

Оказалось, что Sendmail 5 — по крайней мере, поставляемая Sun версия, имевшая ряд улучшений — может работать с sendmail.cf для Sendmail 8, потому что большинство правил остались теми же. Но новые длинные опции конфигурации теперь не распознавались и отбрасывались. И поскольку для большинства из них в бинарнике Sendmail не было значений по умолчанию, программа не находила в sendmail.cf подходящих значений и сбрасывала их на ноль.

Одним из таких обнулённых значений был таймаут подключения к удалённому SMTP-серверу. После экспериментов выяснилось, что на этой конкретной машине при обычной нагрузке нулевой таймаут приводит к разрыву подключения чуть больше, чем через три миллисекунды.

В то время сеть кампуса была полностью построена на свичах. Исходящий пакет не задерживался, пока через протокол POP не попадал на маршрутизатор на другой стороне. То есть длительность подключения к слабонагруженному удалённому хосту в соседней сети по большей части зависела от расстояния, которое покрывалось со скоростью света, а не от случайных задержек маршрутизаторами.

Чувствуя лёгкое головокружение, я ввёл в командной строке:

$ units
1311 units, 63 prefixes

You have: 3 millilightseconds
You want: miles
        * 558.84719
        / 0.0017893979

«500 миль или чуть больше».


Продолжение следует.

Автор: NIX

Источник


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


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