Русский
Русский
English
Статистика
Реклама

Фольклор

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

18.08.2020 10:04:18 | Автор: admin


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

Аллергия автомобиля на ванильное мороженое


История для инженеров, которые понимают, что очевидное не всегда является решением, и что насколько бы факты ни казались неправдоподобными, это всё равно факты. В подразделение Pontiac Division корпорации General Motors поступила жалоба:

Пишу вам второй раз, и не виню вас в том, что вы не отвечаете, ведь это звучит безумно. У нашей семьи есть традиция: каждый вечер после ужина есть мороженое. Сорта мороженого каждый раз меняются, и поужинав, вся семья выбирает, какое мороженое нужно купить, после чего я еду в магазин. Недавно я купил новый Pontiac, и с тех пор мои поездки за мороженым превратились в проблему. Видите ли, каждый раз, когда я покупаю ванильное мороженое и возвращаюсь из магазина, машина не заводится. Если я приношу любое другое мороженое, машина заводится без проблем. Хочу задать серьёзный вопрос, вне зависимости от того, насколько глупо это прозвучит: Что такого есть в Pontiac, из-за чего он не заводится, когда я приношу ванильное мороженое, но при этом легко заводится, если я приношу мороженое с другим вкусом?.

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

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

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

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

Теперь вопрос был к инженеру: почему машина не заводилась, если с того момента, когда заглушили двигатель, проходило меньше времени? Поскольку проблемой стало время, а не ванильное мороженое, инженер быстро нашёл ответ: дело было в газовой пробке. Она возникала каждый вечер, но когда владелец машины тратил на поиск мороженого больше времени, двигатель успевал достаточно охладиться и спокойно заводился. А когда мужчина покупал мороженое, двигатель ещё оставался слишком горячим и газовая пробка не успевала рассосаться.

Мораль: даже совершенно безумные проблемы иногда бывают реальными.

Crash Bandicoot


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

Вот моя история о баге железа.

Для игры Crash Bandicoot я написал код для загрузки и сохранения на карту памяти. Для такого самодовольного разработчика игр это было словно прогуляться по парку: я считал, что работа займёт несколько дней. Однако в результате отлаживал код шесть недель. Попутно я решал и другие задачи, но каждые несколько дней на несколько часов возвращался к этому коду. Это была агония.

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

Спустя некоторое время наш продюсер в Sony, Конни Бус, начала паниковать. Мы не могли отгрузить игру с таким багом, и спустя шесть недель я не понимал, в чём причина этой проблемы. Через Конни мы связались с другими разработчиками PS1: кто-нибудь сталкивался с подобным? Нет. Ни у кого не было проблем с картой памяти.

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

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

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

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

В результате у меня остался небольшой фрагмент кода, с которым всё ещё возникала вышеупомянутая проблема но до сих пор это происходило случайным образом! Чаще всего всё работало нормально, но изредка возникали сбои. Я убрал почти весь код игры, но баг всё ещё жил. Это озадачивало: оставшийся код на самом деле ничего не делал.

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

С помощью часов устройство, которое не связано напрямую с процессором, синхронизируется с исполняемым в процессоре кодом. Часы определяют частоту бодов скорость передачи данных. Если с таймингами возникает путаница, то также путается либо оборудование, либо ПО, либо они оба. И это очень плохо, потому что данные могут повредиться.

А вдруг что-то в нашем коде путает тайминги? Я проверил всё, что с этим связано, в коде тестовой программы, и заметил, что мы задали программируемому таймеру в PS1 частоту 1 кГц (1000 тактов в секунду). Это довольно много, по умолчанию при запуске приставки он работает с частотой 100 Гц. И большинство игр использует именно эту частоту.

Энди, разработчик игры, задал таймеру частоту 1 кГц чтобы движения вычислялись точнее. Энди склонен к чрезмерности, и если мы эмулируем гравитацию, то делаем это настолько точно, насколько возможно!

Но что если ускорение таймера как-то повлияло на общий тайминг программы, а значит и на часы, которые регулируют частоту бодов для карты памяти?

Я закомментировал код с таймером. Ошибка больше не повторялась. Но это не означает, что мы её исправили, ведь сбой возникал случайным образом. А вдруг мне просто повезло?

Через несколько дней я снова экспериментировал с тестовой программой. Баг не повторялся. Я вернулся к полной кодовой базе игры и изменил код сохранения и загрузки так, чтобы программируемый таймер сбрасывался в исходное значение (100 Гц) перед обращением к карте памяти, а затем снова возвращался к 1 кГц. Сбоев больше не возникало.

Но почему так произошло?

Я снова вернулся к тестовой программе. Попробовал найти какую-то закономерность в возникновении ошибки при таймере в 1 кГц. В конце концов я заметил, что ошибка возникает, когда кто-то играет с контроллером PS1. Поскольку сам я редко это делал бы зачем мне контроллер при тестировании кода сохранения и загрузки? то я и не замечал этой зависимости. Но однажды один из наших художников ждал, когда я закончу тестирование, наверняка в тот момент я ругался, и нервно крутил контроллер в руках. Возникла ошибка. Погоди, что?! Ну-ка сделай так снова!.

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

Я пришёл к Конни и рассказал о своём открытии. Она передала информацию одному из инженеров, проектировавшему PS1. Невозможно, ответил он, это не может быть аппаратной проблемой. Я попросил Конни устроить нам беседу.

Инженер позвонил мне, и мы поспорили с ним на его ломаном английском и моём (крайне) ломаном японском. Наконец я сказал: Давайте я просто пришлю свою тестовую программу в 30 строк, при которой движение контроллера приводит к багу. Он согласился. Заявил, что это потеря времени, и что он ужасно занят работой над новым проектом, но уступит, потому что мы очень важный разработчик для Sony. Я подчистил свою тестовую программу и отправил ему.

На следующий вечер (мы были в Лос-Анджелесе, а он в Токио) он позвонил мне и смущённо извинился. Это была аппаратная проблема.

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

Но суть в том, что между компонентами на материнской плате возникали помехи. И при одновременной передаче данных через порт контроллера и порт карты памяти при таймере, работающем с частотой 1 кГц, биты пропадали, данные терялись, а карта повреждалась.

Сбойные коровы


В 1980-х мой ментор Сергей писал ПО для СМ-1800, советского клона PDP-11. Этот микрокомпьютер только что установили на ЖД-станции под Свердловском, важным транспортным узлом СССР. Новая система была спроектирована для маршрутизации вагонов и грузопотоков. Но в ней оказался досадный баг, который приводил к случайным сбоям и падениям. Падения возникали всегда, когда кто-то уходил вечером домой. Но несмотря на тщательное расследование на следующий день, при всех ручных и автоматических тестах компьютер работал корректно. Обычно это свидетельствует о состоянии гонки или каком-то ином баге конкурентности, который проявляется при определённых условиях. Устав от звонков поздней ночью, Сергей решил докопаться до сути, и первым делом понять, какие условия на сортировочной станции приводили к поломке компьютера.

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

Вскоре он выяснил, что сбои возникали только тогда, когда на станции сортировали вагоны с крупным рогатым скотом из северной Украины и западной России, направлявшиеся на близлежащую скотобойню. Это само по себе было странно, ведь скотобойню снабжали хозяйства, находившиеся гораздо ближе, в Казахстане.

Чернобыльская АЭС взорвалась в 1986-м, и радиоактивные осадки сделали непригодными к обитанию прилегающие территории. Загрязнению подверглись обширные территории в северной Украине, Белоруссии и западной России. Заподозрив высокий уровень радиации в прибывающих вагонах, Сергей разработал метод проверки этой теории. Населению иметь дозиметры запрещалось, поэтому Сергей проставился нескольким военным на ЖД-станции. После нескольких порций водки ему удалось убедить солдата измерить уровень радиации в одном из подозрительных вагонов. Оказалось, что уровень в разы превышает обычные значения.

Мало того, что скот сильно фонил радиацией, её уровень был настолько велик, что это приводило к случайному выпадению битов в памяти СМ-1800, которая стояла в здании рядом со станцией.

В СССР возникала нехватка продуктов питания, и власти решили смешивать мясо из Чернобыльской области с мясом из других областей страны. Это позволяло снизить общий уровень радиоактивности без потери ценных ресурсов. Узнав об этом, Сергей сразу же заполнил документы на эмиграцию. А падения компьютера прекратились сами собой, когда уровень радиации снизился со временем.

По трубам


Когда-то компания Movietech Solutions создала ПО для кинотеатров, предназначенное для учёта и продажи билетов и общего управления. DOS-версия флагманского приложения была довольно популярна среди небольших и средних сетей кинотеатров в Северной Америке. Поэтому не удивительно, что когда анонсировали версию под Windows 95, интегрированную с новейшими сенсорными экранами и киосками самообслуживания, а также оснащённую всевозможными средствами отчётности, она тоже быстро стала популярной. Чаще всего обновление проходило без проблем. ИТ-специалисты на местах устанавливали новое оборудование, мигрировали данные, и бизнес продолжался. За исключением случаев, когда не продолжался. Когда такое происходило, компания отправляла Джеймса по прозвищу Чистильщик.

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

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

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

Они не вернулись к старой системе? совершенно серьёзно ответил Джеймс, хотя мысленно он вытаращил глаза от удивления.

Именно: у их айтишника изменились приоритеты и он решил уйти с их старым сервером. Джеймс, они установили систему на шести площадках и только что заплатили за премиальную поддержку, а их бизнес сейчас ведётся как в 1950-х.

Джеймс слегка выпрямился.

Это другое дело. Ладно, приступаю.

Когда он прибыл в Аннаполис, то первым делом нашёл первый кинотеатр клиента, в котором возникла проблема. На взятой в аэропорте карте всё выглядело прилично, но окрестности нужного адреса выглядели подозрительно. Не гетто, но напоминали фильмы в жанре нуар. Когда Джеймс припарковался у обочины в центре, к нему приблизилась проститутка. Учитывая размер Аннаполиса, она, скорее всего, была единственная на весь город. Её появление сразу же напомнило о знаменитом персонаже, который на большом экране предлагал секс за деньги. Нет, не о Джулии Робертс, а о Джоне Войте [намёк на фильм Полуночный ковбой прим. пер.].

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

Боковой вход в кинотеатр находился в промозглой аллее. Джеймс подошёл к двери и постучал. Вскоре она заскрипела и приоткрылась.

Вы чистильщик? раздался изнутри хриплый голос.

Да, это я я приехал, чтобы всё исправить.

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

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

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

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

Затем вошёл один из сотрудников.

Система опять работает.

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

Система лежит.

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


Джеймс нажал кнопку и узор исчез. Он поторопился к билетной кассе и по пути встретил возвращавшегося к нему сотрудника.

Система опять работает.

Если можно мысленно сделать фейспалм, то именно это Джеймс и сделал. Скринсейвер. Он использует OpenGL. И поэтому во время работы потребляет все ресурсы серверного процессора. В результате каждое обращение к серверу завершается по таймауту.

Джеймс вернулся в серверную, залогинился и заменил скринсейвер с прекрасными трубами на пустой экран. То есть вместо скринсейвера, поглощающего 100 % ресурсов процессора, поставил другой, не потребляющий ресурсы. Затем подождал 10 минут, чтобы проверить свою догадку.

Когда Джеймс приехал в следующий кинотеатр, он задумался о том, как объяснить своему руководителю, что он только что пролетел 800 км, чтобы отключить скринсейвер.

Сбой в определённую фазу Луны


Правдивая история. Однажды возник программный баг, который зависел от фазы Луны. Там была маленькая подпрограмма, которая обычно использовалась в разных программах MIT для вычисления приближения к истинной фазе Луны. GLS встроила эту подпрограмму в программу на LISP, которая при записи файла выводила строку с временной меткой длиной почти 80 символов. Очень редко первая строка сообщения получалась слишком длинной и переходила на следующую строку. И когда программа потом читала этот файл, она ругалась. Длина первой строки зависела от точной даты и времени, а также от длины спецификации фазы в момент печати временной метки. То есть баг в буквальном смысле зависел от фазы Луны!

Первое бумажное издание Jargon File (Steele-1983) содержало образец такой строки, приводившей к описанному багу, однако наборщик исправил её. С тех пор это описывают как баг фазы Луны.

Однако будьте осторожны с предположениями. Несколько лет назад инженеры из CERN (European Center for Nuclear Research) столкнулись с ошибками в экспериментах, проводившихся на Большом электрон-позитронном коллайдере. Поскольку компьютеры активно обрабатывают гигантское количество данных, генерируемых этим устройством, прежде чем показать результат учёным, многие предполагали, что ПО каким-то образом чувствительно к фазе Луны. Несколько отчаянных инженеров докопались до истины. Ошибка возникала из-за небольшого изменения геометрии кольца длиной 27 км в связи с деформацией Земли при проходе Луны! Эта история вошла в фольклор физиков как Ньютоновская месть физике частиц и пример связи простейших и старейших физических законов с наиболее передовыми научными концепциями.

Смывание в туалете останавливает поезд


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

Во время одной из проверок инженер, ехавший в поезде, пошёл в туалет. Вскоре он смыл за собой, БУМ! Аварийная остановка.

Инженер связался с машинистом и спросил:

Что ты делал перед самым торможением?

Ну, я притормаживал на спуске

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

Я собираюсь притормаживать.

Ничего не произошло.

Что ты делал при последнем торможении? спросил машинист.

Ну я был в туалете

Ну, тогда иди в туалет и сделай то, что делал, когда будем спускаться опять!

Инженер отправился в туалет, и когда машинист предупредил: Я торможу, он спустил воду. Конечно же, поезд тут же остановился.

Теперь они могли воспроизвести проблему и нужно было найти причину.

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

Шлюз, ненавидевший FORTRAN


Несколько месяцев назад мы заметили, что сетевые подключения к сети на материке [дело было на Гавайях] становились очень-очень медленными. Это могло длиться 10-15 минут, а затем неожиданно возникало снова. Спустя некоторое время мой коллега пожаловался мне, что подключения к сети на материке вообще не работают. У него был какой-то код на FORTRAN, который нужно было скопировать на машину на материке, но это не получалось, потому что сеть не держалась достаточно долго, чтобы завершилась загрузка по FTP.

Да, получалось так, что отказы сети возникали тогда, когда коллега пытался передать по FTP файл с исходным кодом на FORTRAN на машину на материке. Мы попробовали архивировать файл: тогда он спокойно копировался (но на целевой машине не было распаковщика, так что проблема не была решена). Наконец мы разделили код на FORTRAN на очень маленькие фрагменты и отправили их по очереди. Большинство фрагментов скопировалось без проблем, но несколько штук не прошли, либо прошли после многочисленных попыток.

Изучив проблемные фрагменты, мы обнаружили, что у них есть кое-что общее: все они содержат блоки комментариев, которые начинаются и заканчиваются строками, состоящими из прописных букв С (так коллега предпочитал комментировать на FORTRAN). Мы отправили на материк электронные письма специалистам по сетям и попросили о помощи. Конечно, им захотелось увидеть образцы наших файлов, не поддающихся пересылке по FTP но наши письма до них не дошли. Наконец мы придумали просто описать, как выглядят непересылаемые файлы. Это сработало :) [Осмелюсь ли я добавить сюда пример одного из проблемных комментариев на FORTRAN? Наверное, не стоит!]

В конце концов нам удалось разобраться. Между нашей частью кампуса и выходом в сеть на материке недавно установили новый шлюз. У него были ОГРОМНЕ трудности с передачей пакетов, которые содержали повторяющиеся фрагменты из прописных С! Всего несколько таких пакетов могли занять все ресурсы шлюза и не позволяли пробиться большинству других пакетов. Мы пожаловались производителю шлюза и нам ответили: А, да, вы столкнулись с багом повторяющихся С! Мы о нём уже знаем. В конце концов мы решили проблему, купив новый шлюз другого производителя (в защиту первого скажу, что неспособность передавать программы на FORTRAN для кого-то может оказаться преимуществом!).

Трудные времена


Несколько лет назад, работая над созданием ETL-системы на Perl, предназначенной для снижения расходов на третий этап клинических испытаний, мне потребовалось обработать около 40 000 дат. Две из них не прошли проверку. Меня это не слишком обеспокоило, потому что эти даты были взяты из предоставленных клиентом данных, которые часто, скажем так, удивляли. Но когда я проверил исходные данные, оказалось, что этим датами были 1 января 2011 и 1 января 2007. Я подумал, что баг содержится в только что написанной мной программе, но оказалось, ему уже 30 лет. Это может звучать таинственно для тех, кто не знаком с экосистемой программного обеспечения. Из-за давнего решения другой компании, принятого ради заработка денег, мой клиент заплатил мне за исправление бага, который одна компания внесла случайно, а другая намеренно. Чтобы вы поняли, о чём речь, мне нужно рассказать о компании, которая добавила фичу, в результате ставшую багом, а также ещё о нескольких любопытных событиях, внёсших вклад в исправленный мной таинственный баг.

В старые добрые времена компьютеры Apple иногда спонтанно сбрасывали свою дату на 1 января 1904 года. Причина была простой: для отслеживания даты и времени использовались работающие от батарейки системные часы. Что происходило, когда батарейка садилась? Компьютеры начинали отслеживать дату по количеству секунд с начала эпохи. Под эпохой подразумевалась референсная исходная дата, и для Macintoshей это было 1 января 1904. И после умирания батарейки текущая дата сбрасывалась на указанную. Но почему так происходило?

Раньше для хранения количества секунд с исходной даты Apple использовала 32 бита. Один бит может хранить одно из двух значений 1 или 0. Два бита могут хранить одно из четырёх значений: 00, 01, 10, 11. Три бита одно значение из восьми: 000, 001, 010, 011, 100, 101, 110, 111, и т.д. А 32 могли хранить одно из 232 значений, то есть 4 294 967 296 секунд. Для дат по версии Apple это равнялось примерно 136 годам, поэтому старые Маки не могут обрабатывать даты после 2040 года. И если системная батарейка садится, дата сбрасывается на 0 секунд с начала эпохи, и приходится вручную выставлять дату при каждом включении компьютера (или пока вы не купите новую батарейку).

Однако решение Apple хранить даты в виде секунд с начала эпохи означало, что мы не можем обрабатывать даты до начала эпохи, что имело далеко идущие последствия, как мы увидим. Apple ввела фичу, а не баг. Помимо прочего это означало, что операционная система Macintosh была неуязвима для бага миллениума (чего не скажешь про многие приложения для Мака, имевших собственные системы исчисления дат для обхода ограничений).

Идём дальше. Мы использовали Lotus 1-2-3, разработанное IBM киллер-приложение, которое помогло запустить PC-революцию, хотя на Apple-компьютерах была VisiCalc, обеспечившая успех персональным компьютерам. Справедливости ради, если бы 1-2-3 не появилась, PC вряд ли взлетели бы, а история персональных компьютеров могла развиваться бы совсем иначе. Lotus 1-2-3 некорректно обрабатывала 1900-й как високосный год. Когда Microsoft выпустила свою первую электронную таблицу Multiplan, та заняла маленькую долю рынка. И когда запустили проект Excel, решили не только скопировать у Lotus 1-2-3 схему именования строк и колонок, но и обеспечить совместимость по багам, сознательно обрабатывая 1900-й как високосный год. Эта проблема существует по сей день. То есть в 1-2-3 это было багом, а в Excel сознательным решением, которое гарантировало, что все пользователи 1-2-3 могут импортировать свои таблицы в Excel без изменения данных, даже если они ошибочные.

Но тут была ещё одна проблема. Сначала Microsoft выпустила Excel для Macintosh, который не признавал даты до 1 января 1904. А в Excel началом эпохи считалось 1 января 1900 года. Поэтому разработчики внесли изменение, чтобы их программа распознавала вид эпохи и хранила внутри себя данные в соответствии с нужной эпохой. Microsoft даже написала об этом поясняющую статью. И это решение привело к моему багу.

Моя ETL-система получала от покупателей Excel-таблицы, которые создавались под Windows, но могли быть созданы и на Маке. Поэтому началом эпохи в таблице могло быть как 1 января 1900 года, так и 1 января 1904 года. Как это узнать? Формат файла Excel показывает нужную информацию, а парсер который я применял, не показывал (теперь показывает), и предполагал, что вы знаете эпоху для конкретной таблицы. Наверное, можно было потратить больше времени на то, чтобы разобраться в двоичном формате Excel и прислать патч автору парсера, но мне нужно было сделать для клиента много другого, поэтому я быстро написал эвристику для определения эпохи. Она была простой.

В Excel дата 5 июля 1998 может быть представлена в формате 07-05-98 (бесполезная американская система), Jul 5, 98, July 5, 1998, 5-Jul-98 или в каком-нибудь другом бесполезном формате (по иронии судьбы, одним из форматов, который не предлагала моя версия Excel, был стандарт ISO 8601). Однако внутри таблицы неформатированная дата хранилась либо как 35981 для эпохи-1900, либо как 34519 для эпохи-1904 (числа представляют количество дней с начала эпохи). Я просто с помощью простого парсера извлекал год из отформатированной даты, а затем с помощью парсера Excel извлекал год из неотформатированной даты. Если оба значения отличались на 4 года, от я понимал, что использую систему с эпохой-1904.

Почему я не использовал просто отформатированные даты? Потому что 5 июля 1998 может быть отформатировано как July, 98 с потерей дня месяца. Мы получали таблицы от такого количества компаний, которые создавали их такими разными способами, что разбираться с датами должны были мы (в данном случае я). Кроме того, если Excel понимает правильно, то и мы должны!

Тогда же я столкнулся с 39082. Напомню, что Lotus 1-2-3 считал 1900-й високосным, и это добросовестно повторили в Excel. А поскольку это добавляло к 1900-му один день, многие функции вычисления дат могли ошибаться на этот самый день. То есть 39082 могло быть 1 января 2011 (на Маках) или 31 декабря 2006 (в Windows). Если мой парсер лет извлекал из отформатированного значения 2011-й год, то всё хорошо. Но поскольку парсер Excel не знает, какая используется эпоха, он по умолчанию применяет эпоху-1900, возвращая 2006-й год. Моё приложение видело, что разница составляет 5 лет, считало это ошибкой, журналировало и возвращало неотформатированное значение.

Чтобы это обойти, я написал вот это (псевдокод):

diff = formatted_year - parsed_yearif 0 == diff    assume 1900 date systemif 4 == diff    assume 1904 date systemif 5 == diff and month is December and day is 31    assume 1904 date system

И тогда все 40 000 дат отпарсились корректно.

Посреди больших заданий на печать


В начале 1980-х мой отец работал в Storage Technology, в не существующем ныне подразделении, которое создавало ленточные накопители и пневматические системы для высокоскоростной подачи лент.

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

При каждом запуске привода А нужно было вставлять дискету в периферийный дисковод, подключённый к А, чтобы загрузить в его память операционную систему. Она была крайне примитивной: вычислительная мощность обеспечивалась 8-битным микроконтроллером.

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

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

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

Тогда техники позвонили в штаб-квартиру и позвали Эксперта.

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

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

К третьему сбою Эксперт кое-что заметил. Сбой происходил тогда, когда персонал менял ленты в постороннем приводе. Более того, сбой возникал, как только один из сотрудников проходил через определённую плитку на полу.

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

Эксперт понял, что одна из плиток была деформирована. Когда сотрудник наступал на её угол, плитка тёрлась краями о соседние плитки. С ними тёрлись и пластмассовые детали, соединявшие плитки, из-за чего возникали статические микроразряды, создававшие радиочастотные помехи.

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

Это прилив!


История произошла в серверной комнате, на четвёртом или пятом этаже офиса в Портсмуте (кажется), в районе доков.

Однажды упал Unix-сервер с основной базой данных. Его перезагружали, но он радостно продолжал раз за разом падать. Решили позвать кого-нибудь из службы поддержки.

Чувак из поддержки кажется, его звали Марк, но это не важно вряд ли я с ним знаком. Это не важно, правда. Остановимся на Марке, хорошо? Отлично.

Итак, через несколько часов прибыл Марк (от Лидса до Портсмута путь не близкий, знаете ли), включил сервер и всё заработало без проблем. Типичная чёртова поддержка, клиент из-за этого очень расстраивается. Марк просматривает файлы журнала и не находит ничего предосудительного. Тогда Марк возвращается на поезд (или на каком там виде транспорта он приехал, это могла быть и хромая корова, насколько я знаю короче, это не важно, ладно?) и отправляется обратно в Лидс, впустую потратив день.

Тем же вечером сервер падает снова. История та же сервер не поднимается. Марк удалённо пытается помочь, но клиент не может запустить сервер.

Опять поезд, автобус, лимонное безе или ещё какая-то херня, и Марк опять в Портсмуте. Глянь-ка, сервер загружается без проблем! Чудо. Марк несколько часов проверяет, что с операционкой или ПО всё в порядке, и отправляется в Лидс.

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

Ситуация повторялась несколько дней. Сервер работает, примерно через 10 часов падает и не запускается в течение следующих 2 часов. Они проверили охлаждение, утечки памяти, они проверили всё, но ничего не нашли. Затем сбои прекратились.

Неделя прошла беззаботно все были счастливы. Счастливы, пока всё не началось опять. Картина та же. 10 часов работы, 23 часа простоя

А потом кто-то (кажется, мне говорили, что этот человек не имел отношения к ИТ) сказал:

Это прилив!

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

Он перестаёт работать с приливом.

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

На прошлой неделе прилив был низким, а на этой высоким.

Немного терминологии для тех, у кого нет лицензии на управление яхтой. Приливы зависят от лунного цикла. И по мере вращения Земли, каждые 12,5 часа гравитационное притяжение Солнца и Луны создаёт приливную волну. В начале 12,5-часового цикла возникает прилив, в середине цикла отлив, а в конце снова прилив. Но поскольку орбита Луны меняется, меняется и разница между отливом и приливом. Когда Луна находится между Солнцем и Землёй или с противоположной стороны от Земли (полнолуние или отсутствие Луны), мы получаем сизигийские приливы самые высокие приливы и самые низкие отливы. В полулуние мы получаем квадратурные приливы самые низкие приливы. Разница между двумя экстремумами сильно уменьшается. Лунный цикл длится 28 дней: сизигийские квадратурные сизигийские квадратурные.

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

Полётное задание для ракеты


Мне поручили портировать большую (около 400 тыс. строк) систему управления и контроля запуска ракет под новые версии операционной системы, компилятора и языка. Точнее, с Solaris 2.5.1 на Solaris 7, и с Verdix Ada Development System (VADS), написанной на Ada 83, на систему Rational Apex Ada, написанную на Ada 95. VADS была куплена компанией Rational, и её продукт устарел, хотя Rational постаралась реализовать совместимые версии специфических для VADS пакетов, чтобы облегчить переход на компилятор Apex.

Три человека помогали мне просто получить чисто скомпилированный код. На это ушло две недели. А затем я самостоятельно работал над тем, чтобы заставить систему работать. Короче, это были худшие архитектура и реализация программной системы, что мне встречались, поэтому на завершение портирования ушло ещё два месяца. Затем систему передали на тестирование, что заняло ещё несколько месяцев. Я сразу исправлял баги, которые находили при тестировании, но их количество быстро снизилось (исходный код был production-системой, поэтому его функциональность работала достаточно надёжно, мне лишь пришлось убрать баги, возникшие при адаптации под новый компилятор). В конце концов, когда всё работало так, как и должно было, меня перевели на другой проект.

А в пятницу перед Днём благодарения раздался телефонный звонок.

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

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

И внимание было обращено на меня, как на человека, портировавшего систему.

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

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

Поскольку в журналах не было ничего интересного, решили попытаться воспроизвести проблему в местной лаборатории. Это была непростая задача, поскольку событие возникало примерно один раз на 1000 прогонов. Одной из предполагаемых причин было то, что вызов разработанной поставщиком мьютекс-функции (часть пакета миграции VADS) Unlock не приводил к разблокированию. Поток обработки, вызывавший функцию, обрабатывал heartbeat-сообщения, которые номинально приходили каждую секунду. Мы подняли частоту до 10 Гц, то есть 10 раз в секунду, и начали прогон. Примерно через час система заблокировалась. В журнале мы увидели, что последовательность записанных сообщений была такой же, как во время сбойного испытания. Сделали ещё несколько прогонов, система стабильно блокировалась через 4590 минут после начала, и каждый раз в журнале была одна и та же трасса. Несмотря на то, что технически мы сейчас исполняли другой код частота сообщений была другой поведение системы повторялось, поэтому мы уверились, что этот нагрузочный сценарий приводил к той же проблеме.

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

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

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

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

Также при изучении журнала выяснилось, что, несмотря на зависание обработки heartbeat-сообщений, которое блокировало все операции ввода-вывода процесса и не давало выполнять другие обработки, прочие независимые задачи продолжали исполняться. То есть работа блокировалась не целиком, только (критическая) цепочка задач.

Это была зацепка, необходимая для вычисления блокирующего выражения.

Я сделал Ada-пакет, который содержал задачу, перечисляемый тип и глобальную переменную этого типа. Перечисляемые литералы были привязаны к конкретным выражениям проблемной последовательности (например, Incrementing_Buffer_Index, Locking_Mutex, Mutex_Unlocked), а затем вставил в неё выражения присваивания, которые присваивали соответствующее перечисление глобальной переменной. Поскольку объектный код всего этого просто хранил постоянную в памяти, переключение задачи в результате его исполнения было крайне маловероятно. В первую очередь мы подозревали выражения, которые могли переключить задачу, поскольку блокировка возникала при исполнении, а не возвращении при обратном переключении задачи (по нескольким причинам).

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

Ожидалось, что когда система дойдёт до исполнения проблемного кода, глобальная переменная будет сбрасываться при переходе к каждому следующему выражению. Затем произойдёт что-то, приводящее к переключению задачи, и поскольку частота его исполнения (10 Гц) ниже, чем у мониторящей задачи, монитор мог зафиксировать значение глобальной переменной и записать его. В нормальной ситуации я мог бы получить повторяющуюся последовательность подмножества перечислений: последние значения переменной на момент переключения задачи. При зависании глобальная переменная не должна больше меняться, и последнее записанное значение покажет, исполнение какого выражения не завершилось.

Запустил код с отслеживанием. Он завис. А мониторинг сработал как по маслу.

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

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

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

Вставил в код и запустил тест. Семь часов спустя код продолжал работать.

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

Это была самая многолюдная проверка кода в моей карьере :) В комнате со мной было около десяти инженеров и менеджеров, ещё десяток людей подключились по конференц-звонку и все они исследовали около 20 строк кода.

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

Ладно, это всё хорошо и прекрасно, но в чём суть этой истории?

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

Я был не единственным, кто работал над этой проблемой, хотя и находился в центре внимания, поскольку делал портирование. Но хотя я и делал его, это не означает, что я разбирался со всеми сотнями тысяч строк кода, или хотя бы бегло просмотрел их. Код и журналы анализировали инженеры по всей стране, но когда они говорили мне свои гипотезы о причинах сбоя, мне хватало полминуты, чтобы опровергнуть их. И когда меня просили проанализировать теории, я перекладывал это на кого-нибудь другого, потому что мне было очевидно, что эти инженеры идут не тем путём. Звучит самонадеянно? Да, это так, но я отклонил гипотезы и запросы по другой причине.

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

За годы работы я накопил много знаний и опыта. Я был одним из пионеров использования Ada, понимал её достоинства и недостатки. Я знаю, как runtime-библиотеки Ada обрабатывают задачи и работают с параллельным исполнением. И я разбираюсь в низкоуровневом программировании на уровне памяти, регистров и ассемблера. Иными словами, у меня глубокие познания в своей сфере. И я использовал их для поиска причины проблемы. Я не просто обошёл баг, а понимал, как найти его в условиях очень чувствительной среды исполнения.

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

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

И тогда у вас будет своя испорченная праздничная неделя.



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

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

20.08.2020 10:19:07 | Автор: admin

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

Больше магии


Несколько лет назад я шарился по шкафам, в которых располагался компьютер 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=0o=- 20047 20047 IN IP4 10.41.22.248s=SDP datac=IN IP4 10.41.22.248t=0 0m=audio 11786 RTP/AVP 18 0 18 9 9 101a=rtpmap:18 G729/8000a=fmtp:18 annexb=noa=rtpmap:0 PCMU/8000a=rtpmap:18 G729/8000a=fmtp:18 annexb=noa=rtpmap:9 G722/8000a=rtpmap:9 G722/8000a=fmtp:101 0-15a=rtpmap:101 telephone-event/8000a=ptime:20a=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. Во время игры экран закрашивался чёрным цветом с белой окантовкой. Каждая линия представляет одного из игроков. Мы отображали игровые баллы в строке в нижней части экрана. Графически это была не самая продвинутая программа, но зато простая и весёлая. Выглядела она так:


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

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


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

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


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

Побег


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

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

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


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

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

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

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


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

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

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


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


Здесь показан верхний левый угол экрана. Цвет $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 не попадал на маршрутизатор на другой стороне. То есть длительность подключения к слабонагруженному удалённому хосту в соседней сети по большей части зависела от расстояния, которое покрывалось со скоростью света, а не от случайных задержек маршрутизаторами.

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

$ units1311 units, 63 prefixesYou have: 3 millilightsecondsYou want: miles        * 558.84719        / 0.0017893979

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



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

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

24.08.2020 10:10:39 | Автор: admin


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

Маленький SSH, который (иногда) не мог


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

В компании AdGear Technologies Inc., где я работал, королём был SSH. Мы применяли его для управления, мониторинга, развёртывания, сбора журналов, даже для потокового вещания в реальном времени. Этот протокол устойчивый и надёжный, обладает предсказуемостью нативного Unix-инструмента и просто работает.

Но однажды случайные Cron-письма подсказали нам, что протокол не работает.

Таймаут


На машинах в нашем лондонском ЦОДе возникали случайные сбои при отправке файлов журналов в ЦОД в Монреале. Эта задача периодически запускалась из Cron, и сбой проявлялся так:

  • Cron-письма сообщали о проблемах с SSH.
    • Иногда он зависает.
    • Иногда завершается без ошибки по таймауту.
  • При внутренней проверке работоспособности Nagios предупреждает об отсутствующих данных в Монреале.

Мы залогинились на лондонские машины, вручную запустили команду push и она успешно отработала. Мы списали это на временные сетевые неполадки.

Таймауты


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

Между Лондоном и Монреалем было 17 хопов. Мы создали профиль задержек и потерь пакетов. Оказалось, что на паре хопов терялось 1-3 % пакетов. Вместе с отделом эксплуатации лондонского ЦОДа мы заявку на перемаршрутизацию.

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

Апокалипсис


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

Убрав из уравнения большую часть данных, мы смогли воссоздать сценарий с помощью простого ванильного SSH. В лондонском ЦОДе сервер SSH mtl-machine либо моментально выполнял задачу, либо повисал и не мог установить соединение. Удивление начало расти.

Куда делись пакеты?


Мы трижды проверили конфигурацию SSH-севера и работу систем в Монреале:

  • DNS-серверы отвечали быстро.
  • Зона обратного просмотра DNS была отключена.
  • Максимальное количество клиентских подключений было достаточно велико.
  • Нас не атаковали.
  • Канал не был забит.

Кроме того, даже если что-то не работало, мы наблюдали бы зависания при работе с двумя разными ЦОДами в Монреале. Причём наши нелондонские ЦОДы успешно общались с Монреалем. То есть проблема была связана с Лондоном.

Мы запустили tcpdump и стали наблюдать за пакетами. Нас интересовала общая динамика и данные, полученные с помощью Pcaps и загруженные в Wireshark. Мы увидели признаки потери пакетов и повторной отправки, но всё было на минимальном уровне и вызывало беспокойства.

Затем мы целиком проанализировали подключения в ситуациях, когда связь по SSH устанавливалась успешно, а затем подключения в ситуациях, когда связь по SSH зависала.

Когда подключение из Лондона к Монреалю зависло, мы пришли к таким выводам:

  • Установка связи по TCP прошла нормально.
  • Туда-обратно была передана служебная SSH-информация. Где нужно, были нормальные TCP ack-пакеты.
  • Определённый пакет был отправлен из Лондона и получен в Монреале.
  • Тот же пакет был несколько раз заново отправлен из Лондона и получен в Монреале.
  • Просто Монреаль на это не отвечает!

Было непонятно, почему не отвечает Монреаль (из-за этого Лондон отправляет данные заново). На этом соединение зависло, потому что завис протокол 4 уровня. Ещё более волнительным было то, что если в Лондоне прервать повторную SSH-отправку и сразу же её перезапустить, то она успешно отработает. В этом случае tcpdump показывал, что Монреаль получил пакет и ответил на это, и работа продолжалась.

На SSH-клиенте в Лондоне мы включили подробную отладку (-vvv), и после этих записей в журнале связь повисла:

debug2: kex_parse_kexinit: first_kex_follows 0 debug2: kex_parse_kexinit: reserved 0 debug2: mac_setup: found hmac-md5debug1: kex: server->client aes128-ctr hmac-md5 nonedebug2: mac_setup: found hmac-md5debug1: kex: client->server aes128-ctr hmac-md5 nonedebug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sentdebug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP

Мы погуглили по запросу SSH hang SSH2_MSG_KEX_DH_GEX_GROUP и получили много результатов: от проблем с Wi-Fi до TCPбагов в Windows и глючных маршрутизаторов, теряющих TCP-фрагменты. В качестве одного из решений для LAN предлагалось вычислить MSS пути и задать это значение в качестве MTU на обоих концах маршрута.

Я продолжал уменьшать MTU на лондонском сервере с 1500 это не помогало до тех пор, пока я не дошёл до волшебного значения 576. После этого зависание SSH больше не повторялось. У меня исполнялся скрипт с SSH-циклом, и по желанию я мог вызвать таймауты, вернув MTU значение 1500, или избавиться от них, задав 576. К сожалению, это публичные рекламные серверы, и глобальное назначение MTU 1500 не решит проблемы. Однако выше уже упоминалось, что, вероятно, где-то сломан процесс фрагментации или пересборки пакетов.

Вернёмся к проверке полученных пакетов с помощью tcpdump: признаков фрагментации не было. Размер полученного пакета полностью соответствовал размеру отправленного. Если что-то фрагментировало пакет на байте 576+, то что-то успешно собирало его обратно.

Мерцай-мерцай, кривая звёздочка


Углубившись в анализ, я изучал полные дампы пакетов (tcpdump -s 0 -X), а не одни лишь заголовки. При сравнении волшебного пакета из успешной отправки с пакетом из сбойной отправки, я почти не нашёл различий, за исключением TCP/IP-заголовков. Но было очевидно, что это первый пакет в TCP-соединении, содержавший достаточно данных для прохода через отметку в 576 байтов. Все предыдущие пакеты были гораздо меньше.

Сравнив тот же пакет из сбойной отправки, в том виде, в каком он покинул Лондон и пришёл в Монреаль, мой взгляд за что-то зацепился. За что-то малозаметное, и я отмахнулся из-за усталости (был поздний вечер пятницы). Но после нескольких обновлений и сравнений мне уже не мерещилось.

Так выглядел пакет, покинувший Лондон (минус несколько первых байтов, идентифицирующих IP-адреса)
0x0040: 0b7c aecc 1774 b770 ad92 0000 00b7 6563 .|...t.p......ec
0x0050: 6468 2d73 6861 322d 6e69 7374 7032 3536 dh-sha2-nistp256
0x0060: 2c65 6364 682d 7368 6132 2d6e 6973 7470 ,ecdh-sha2-nistp
0x0070: 3338 342c 6563 6468 2d73 6861 322d 6e69 384,ecdh-sha2-ni
0x0080: 7374 7035 3231 2c64 6966 6669 652d 6865 stp521,diffie-he
0x0090: 6c6c 6d61 6e2d 6772 6f75 702d 6578 6368 llman-group-exch
0x00a0: 616e 6765 2d73 6861 3235 362c 6469 6666 ange-sha256,diff
0x00b0: 6965 2d68 656c 6c6d 616e 2d67 726f 7570 ie-hellman-group
0x00c0: 2d65 7863 6861 6e67 652d 7368 6131 2c64 -exchange-sha1,d
0x00d0: 6966 6669 652d 6865 6c6c 6d61 6e2d 6772 iffie-hellman-gr
0x00e0: 6f75 7031 342d 7368 6131 2c64 6966 6669 oup14-sha1,diffi
0x00f0: 652d 6865 6c6c 6d61 6e2d 6772 6f75 7031 e-hellman-group1
0x0100: 2d73 6861 3100 0000 2373 7368 2d72 7361 -sha1...#SSH-rsa
0x0110: 2c73 7368 2d64 7373 2c65 6364 7361 2d73 ,SSH-dss,ecdsa-s
0x0120: 6861 322d 6e69 7374 7032 3536 0000 009d ha2-nistp256
0x0130: 6165 7331 3238 2d63 7472 2c61 6573 3139 aes128-ctr,aes19
0x0140: 322d 6374 722c 6165 7332 3536 2d63 7472 2-ctr,aes256-ctr
0x0150: 2c61 7263 666f 7572 3235 362c 6172 6366 ,arcfour256,arcf
0x0160: 6f75 7231 3238 2c61 6573 3132 382d 6362 our128,aes128-cb
0x0170: 632c 3364 6573 2d63 6263 2c62 6c6f 7766 c,3des-cbc,blowf
0x0180: 6973 682d 6362 632c 6361 7374 3132 382d ish-cbc,cast128-
0x0190: 6362 632c 6165 7331 3932 2d63 6263 2c61 cbc,aes192-cbc,a
0x01a0: 6573 3235 362d 6362 632c 6172 6366 6f75 es256-cbc,arcfou
0x01b0: 722c 7269 6a6e 6461 656c 2d63 6263 406c r,rijndael-cbc@l
0x01c0: 7973 6174 6f72 2e6c 6975 2e73 6500 0000 ysator.liu.se
0x01d0: 9d61 6573 3132 382d 6374 722c 6165 7331 .aes128-ctr,aes1
0x01e0: 3932 2d63 7472 2c61 6573 3235 362d 6374 92-ctr,aes256-ct
0x01f0: 722c 6172 6366 6f75 7232 3536 2c61 7263 r,arcfour256,arc
0x0200: 666f 7572 3132 382c 6165 7331 3238 2d63 four128,aes128-c
0x0210: 6263 2c33 6465 732d 6362 632c 626c 6f77 bc,3des-cbc,blow
0x0220: 6669 7368 2d63 6263 2c63 6173 7431 3238 fish-cbc,cast128
0x0230: 2d63 6263 2c61 6573 3139 322d 6362 632c -cbc,aes192-cbc,
0x0240: 6165 7332 3536 2d63 6263 2c61 7263 666f aes256-cbc,arcfo
0x0250: 7572 2c72 696a 6e64 6165 6c2d 6362 6340 ur,rijndael-cbc@
0x0260: 6c79 7361 746f 722e 6c69 752e 7365 0000 lysator.liu.se
0x0270: 00a7 686d 6163 2d6d 6435 2c68 6d61 632d ..hmac-md5,hmac-
0x0280: 7368 6131 2c75 6d61 632d 3634 406f 7065 sha1,umac-64@ope
0x0290: 6e73 7368 2e63 6f6d 2c68 6d61 632d 7368 nSSH.com,hmac-sh
0x02a0: 6132 2d32 3536 2c68 6d61 632d 7368 6132 a2-256,hmac-sha2
0x02b0: 2d32 3536 2d39 362c 686d 6163 2d73 6861 -256-96,hmac-sha
0x02c0: 322d 3531 322c 686d 6163 2d73 6861 322d 2-512,hmac-sha2-
0x02d0: 3531 322d 3936 2c68 6d61 632d 7269 7065 512-96,hmac-ripe
0x02e0: 6d64 3136 302c 686d 6163 2d72 6970 656d md160,hmac-ripem
0x02f0: 6431 3630 406f 7065 6e73 7368 2e63 6f6d d160@openSSH.com
0x0300: 2c68 6d61 632d 7368 6131 2d39 362c 686d ,hmac-sha1-96,hm
0x0310: 6163 2d6d 6435 2d39 3600 0000 a768 6d61 ac-md5-96....hma
0x0320: 632d 6d64 352c 686d 6163 2d73 6861 312c c-md5,hmac-sha1,
0x0330: 756d 6163 2d36 3440 6f70 656e 7373 682e umac-64@openSSH.
0x0340: 636f 6d2c 686d 6163 2d73 6861 322d 3235 com,hmac-sha2-25
0x0350: 362c 686d 6163 2d73 6861 322d 3235 362d 6,hmac-sha2-256-
0x0360: 3936 2c68 6d61 632d 7368 6132 2d35 3132 96,hmac-sha2-512
0x0370: 2c68 6d61 632d 7368 6132 2d35 3132 2d39 ,hmac-sha2-512-9
0x0380: 362c 686d 6163 2d72 6970 656d 6431 3630 6,hmac-ripemd160
0x0390: 2c68 6d61 632d 7269 7065 6d64 3136 3040 ,hmac-ripemd160@
0x03a0: 6f70 656e 7373 682e 636f 6d2c 686d 6163 openSSH.com,hmac
0x03b0: 2d73 6861 312d 3936 2c68 6d61 632d 6d64 -sha1-96,hmac-md
0x03c0: 352d 3936 0000 0015 6e6f 6e65 2c7a 6c69 5-96....none,zli
0x03d0: 6240 6f70 656e 7373 682e 636f 6d00 0000 b@openSSH.com
0x03e0: 156e 6f6e 652c 7a6c 6962 406f 7065 6e73 .none,zlib@opens
0x03f0: 7368 2e63 6f6d 0000 0000 0000 0000 0000 sh.com
0x0400: 0000 0000 0000 0000 0000 0000

А так выглядел тот же пакет, пришедший в Монреаль
0x0040: 0b7c aecc 1774 b770 ad92 0000 00b7 6563 .|...t.p......ec
0x0050: 6468 2d73 6861 322d 6e69 7374 7032 3536 dh-sha2-nistp256
0x0060: 2c65 6364 682d 7368 6132 2d6e 6973 7470 ,ecdh-sha2-nistp
0x0070: 3338 342c 6563 6468 2d73 6861 322d 6e69 384,ecdh-sha2-ni
0x0080: 7374 7035 3231 2c64 6966 6669 652d 6865 stp521,diffie-he
0x0090: 6c6c 6d61 6e2d 6772 6f75 702d 6578 6368 llman-group-exch
0x00a0: 616e 6765 2d73 6861 3235 362c 6469 6666 ange-sha256,diff
0x00b0: 6965 2d68 656c 6c6d 616e 2d67 726f 7570 ie-hellman-group
0x00c0: 2d65 7863 6861 6e67 652d 7368 6131 2c64 -exchange-sha1,d
0x00d0: 6966 6669 652d 6865 6c6c 6d61 6e2d 6772 iffie-hellman-gr
0x00e0: 6f75 7031 342d 7368 6131 2c64 6966 6669 oup14-sha1,diffi
0x00f0: 652d 6865 6c6c 6d61 6e2d 6772 6f75 7031 e-hellman-group1
0x0100: 2d73 6861 3100 0000 2373 7368 2d72 7361 -sha1...#SSH-rsa
0x0110: 2c73 7368 2d64 7373 2c65 6364 7361 2d73 ,SSH-dss,ecdsa-s
0x0120: 6861 322d 6e69 7374 7032 3536 0000 009d ha2-nistp256
0x0130: 6165 7331 3238 2d63 7472 2c61 6573 3139 aes128-ctr,aes19
0x0140: 322d 6374 722c 6165 7332 3536 2d63 7472 2-ctr,aes256-ctr
0x0150: 2c61 7263 666f 7572 3235 362c 6172 6366 ,arcfour256,arcf
0x0160: 6f75 7231 3238 2c61 6573 3132 382d 6362 our128,aes128-cb
0x0170: 632c 3364 6573 2d63 6263 2c62 6c6f 7766 c,3des-cbc,blowf
0x0180: 6973 682d 6362 632c 6361 7374 3132 382d ish-cbc,cast128-
0x0190: 6362 632c 6165 7331 3932 2d63 6263 2c61 cbc,aes192-cbc,a
0x01a0: 6573 3235 362d 6362 632c 6172 6366 6f75 es256-cbc,arcfou
0x01b0: 722c 7269 6a6e 6461 656c 2d63 6263 406c r,rijndael-cbc@l
0x01c0: 7973 6174 6f72 2e6c 6975 2e73 6500 0000 ysator.liu.se
0x01d0: 9d61 6573 3132 382d 6374 722c 6165 7331 .aes128-ctr,aes1
0x01e0: 3932 2d63 7472 2c61 6573 3235 362d 6374 92-ctr,aes256-ct
0x01f0: 722c 6172 6366 6f75 7232 3536 2c61 7263 r,arcfour256,arc
0x0200: 666f 7572 3132 382c 6165 7331 3238 2d63 four128,aes128-c
0x0210: 6263 2c33 6465 732d 6362 632c 626c 6f77 bc,3des-cbc,blow
0x0220: 6669 7368 2d63 6263 2c63 6173 7431 3238 fish-cbc,cast128
0x0230: 2d63 6263 2c61 6573 3139 322d 6362 632c -cbc,aes192-cbc,
0x0240: 6165 7332 3536 2d63 6263 2c61 7263 666f aes256-cbc,arcfo
0x0250: 7572 2c72 696a 6e64 6165 6c2d 6362 7340 ur,rijndael-cbs@
0x0260: 6c79 7361 746f 722e 6c69 752e 7365 1000 lysator.liu.se
0x0270: 00a7 686d 6163 2d6d 6435 2c68 6d61 732d ..hmac-md5,hmas-
0x0280: 7368 6131 2c75 6d61 632d 3634 406f 7065 sha1,umac-64@ope
0x0290: 6e73 7368 2e63 6f6d 2c68 6d61 632d 7368 nSSH.com,hmac-sh
0x02a0: 6132 2d32 3536 2c68 6d61 632d 7368 7132 a2-256,hmac-shq2
0x02b0: 2d32 3536 2d39 362c 686d 6163 2d73 7861 -256-96,hmac-sxa
0x02c0: 322d 3531 322c 686d 6163 2d73 6861 322d 2-512,hmac-sha2-
0x02d0: 3531 322d 3936 2c68 6d61 632d 7269 7065 512-96,hmac-ripe
0x02e0: 6d64 3136 302c 686d 6163 2d72 6970 756d md160,hmac-ripum
0x02f0: 6431 3630 406f 7065 6e73 7368 2e63 7f6d d160@openSSH.c.m
0x0300: 2c68 6d61 632d 7368 6131 2d39 362c 786d ,hmac-sha1-96,xm
0x0310: 6163 2d6d 6435 2d39 3600 0000 a768 7d61 ac-md5-96....h}a
0x0320: 632d 6d64 352c 686d 6163 2d73 6861 312c c-md5,hmac-sha1,
0x0330: 756d 6163 2d36 3440 6f70 656e 7373 782e umac-64@openssx.
0x0340: 636f 6d2c 686d 6163 2d73 6861 322d 3235 com,hmac-sha2-25
0x0350: 362c 686d 6163 2d73 6861 322d 3235 362d 6,hmac-sha2-256-
0x0360: 3936 2c68 6d61 632d 7368 6132 2d35 3132 96,hmac-sha2-512
0x0370: 2c68 6d61 632d 7368 6132 2d35 3132 3d39 ,hmac-sha2-512=9
0x0380: 362c 686d 6163 2d72 6970 656d 6431 3630 6,hmac-ripemd160
0x0390: 2c68 6d61 632d 7269 7065 6d64 3136 3040 ,hmac-ripemd160@
0x03a0: 6f70 656e 7373 682e 636f 6d2c 686d 7163 openSSH.com,hmqc
0x03b0: 2d73 6861 312d 3936 2c68 6d61 632d 7d64 -sha1-96,hmac-}d
0x03c0: 352d 3936 0000 0015 6e6f 6e65 2c7a 7c69 5-96....none,z|i
0x03d0: 6240 6f70 656e 7373 682e 636f 6d00 0000 b@openSSH.com
0x03e0: 156e 6f6e 652c 7a6c 6962 406f 7065 6e73 .none,zlib@opens
0x03f0: 7368 2e63 6f6d 0000 0000 0000 0000 0000 sh.com
0x0400: 0000 0000 0000 0000 0000 0000

Что-нибудь заметили? Если нет, то ничего страшного. Можете скопировать в два окна в текстовом редакторе и быстро переключаться между ними, чтобы увидеть изменения символов.

Так-так. Это не потеря пакетов, а их повреждение! Очень небольшое, очень предсказуемое повреждение. Интересные наблюдения:

  • Начальная часть пакета (<576 байтов) не повреждена.
  • Повреждён каждый 15-й байт из 16.
  • Повреждение предсказуемое. Все h стали x, все c стали s.

Вы могли уже свериться с ASCII-таблицей и прийти к выводу: один бит застрял на значении 1. Превращение в 1 четвёртого бита в байте портит предыдущие буквы слева до значений справа.

Очевидные виновники в нашем поле зрения (NIC-карты, принимающие серверы) вне подозрений, потому что у сбоя есть закономерность (несколько лондонских машин несколько монреальских ЦОДов и машин). Причина должна быть на маршруте и ближе к Лондону.

Ситуация начала обретать смысл. Я также заметил маленькую подсказку в подробном режиме tcpdump (tcp cksum bad), которую раньше не замечал. Монреальский сервер отбрасывал пакет на уровне ядра, когда понимал, что тот повреждён, и не передавал пакет SSH-демону в пользовательском пространстве. Затем Лондон снова отправлял пакет, тот опять повреждался, а Монреаль молча его отбрасывал. С точки зрения SSH и SSHd, соединение зависло. С точки зрения tcpdump, потерь не было, а монреальские серверы просто игнорируют данные.

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

Фиксы поздней ночью в пятницу приятны, потому что на выходных можно расслабиться и не думать о проблемах и службе поддержки :)

Где Вальдо?


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

Обновление лондонских маршрутов, чтобы трафик не шёл по старому пути, означало, что я не смогу легко воспроизвести проблему. Я нашёл в Монреале друга с подходящей машиной под FreeBSD, которая была доступна из Лондона по старым маршрутам.

Я хотел убедиться в предсказуемости повреждения даже без участия SSH. Мне это легко удалось с помощью нескольких конвейеров.

В Монреале:

nc -l -p 4000 > /dev/null

Затем в Лондоне:

cat /dev/zero | nc mtl 4000

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

Мы отправляли просто пакет нулей
0x0210
0x0220 0000 0000 0000 0000 0000 0000 0000 0000
0x0230 0000 0000 0000 0000 0000 0000 0000 0000
0x0240 0000 0000 0000 0000 0000 0000 0000 0000
0x0250 0000 0000 0000 0000 0000 0000 0000 1000
0x0260 0000 0000 0000 0000 0000 0000 0000 1000
0x0270 0000 0000 0000 0000 0000 0000 0000 1000
0x0280 0000 0000 0000 0000 0000 0000 0000 1000
0x0290 0000 0000 0000 0000 0000 0000 0000 1000
0x02a0 0000 0000 0000 0000 0000 0000 0000 1000
0x02b0 0000 0000 0000 0000 0000 0000 0000 1000
0x02c0 0000 0000 0000 0000 0000 0000 0000 1000
0x02d0 0000 0000 0000 0000 0000 0000 0000 1000
0x02e0 0000 0000 0000 0000 0000 0000 0000 1000
0x02f0 0000 0000 0000 0000 0000 0000 0000 1000
0x0300 0000 0000 0000 0000 0000 0000 0000 1000
0x0310 0000 0000 0000 0000 0000 0000 0000 1000
0x0320 0000 0000 0000 0000 0000 0000 0000 1000
0x0330 0000 0000 0000 0000 0000 0000 0000 1000
0x0340 0000 0000 0000 0000 0000 0000 0000 1000
0x0350 0000 0000 0000 0000 0000 0000 0000 1000
0x0360 0000 0000 0000 0000 0000 0000 0000 1000
0x0370 0000 0000 0000 0000 0000 0000 0000 1000
0x0380 0000 0000 0000 0000 0000 0000 0000 1000
0x0390 0000 0000 0000 0000 0000 0000 0000 1000
0x03a0 0000 0000 0000 0000 0000 0000 0000 1000
0x03b0 0000 0000 0000 0000 0000 0000 0000 1000
0x03c0 0000 0000 0000 0000 0000 0000 0000 1000
0x03d0 0000 0000 0000 0000 0000 0000 0000 0000
0x03e0

Воспроизведя баг, мне нужно было найти тот из 17 хопов, на котором возникало повреждение. Я не мог просто позвонить провайдерам всех кластеров и попросить проверить их системы.

Я решил последовательно пропинговать каждый роутер, вдруг это поможет. Написал специальные ICMP-пакеты, достаточно большие, чтобы превысить безопасный рубеж в 576 байтов, и заполнил их нулями. Затем с помощью этих пакетов пропинговал из Лондона монреальский сервер.

Пакеты вернулись неповреждёнными.

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

В netcat-конвейерах я заменил TCP на UDP. Опять никаких повреждений.

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

Казалось, нельзя было выявить со стороны сбойный хоп. Или можно?

Mirror mirror on the wall


Чтобы определить, возникает ли повреждение, нужно было воспользоваться одним из сценариев:

  • Через TCP-узел, с которым идёт взаимодействие, проверить пакет в пункте назначения.
    • Не в пользовательском пространстве, куда пакет не будет доставлен в случае ошибки при проверке контрольной суммы, а проверить полученный пакет на повреждения с помощью root и tcpdump.
  • С помощью TCP-узла, который работает как echo-сервер и зеркалирует обратно полученные данные, проверить пакет на отправляющей ноде.

Неожиданно выяснилось, что нам доступна вторая точка замера. Доступна не напрямую, но всё же: при самом первом подходе к решению проблемы мы заметили, что SSH-клиенты виснут, когда общаются с SSH-серверами через повреждающий хоп. Это хороший пассивный сигнал, который можно использовать вместо активного echo-сигнала.

И в этом нам могут помочь многочисленные открытые SSH-серверы в интернете.

Нам не нужны актуальные аккаунты на этих серверах, нужно лишь запустить SSH-соединение, посмотреть, будет ли успешной фаза обмена шифрами (при разумном количестве повторных попыток, чтобы учесть случайность повреждения).

План был такой:

  • Использовать замечательный инструмент nmap в режиме random IP для составления списка географически распределённых открытых SSH-серверов.
  • Протестировать каждый сервер и выяснить:
    • Если он не отвечает, непредсказуем или использует файрвол игнорировать его.
    • Если успешно общается после N-попыток пометить хорошим.
    • Если общается с зависаниями в фазе telltale после N-попыток пометить плохим.
  • Запомнить трассировку для хороших и плохих.

Я думал так: в трассировках всех плохих серверов будут использоваться несколько одинаковых хопов. Мы сможем выделить подозрительные хопы и определить те из них, которые используются в трассировках хороших серверов. В надежде, что останется один или два.

Потратив час на классификацию серверов вручную, я прекратил исследовать данные. У меня было 16 плохих и 25 хороших серверов.

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

Теме не менее, до него было два провайдера: Лондон N хопов upstream1 Y хопов upstream2.

Это был первый из Y хопов в upstream2, прямо на границе между upstream1 и upstream2. Он повреждал случайные TCP-пакеты, что приводило к многочисленным повторным отправкам и, в зависимости от особенностей обмена данными протокола, к зависаниям или снижению объёмов передачи.

Вместе с отделом эксплуатации лондонского ЦОДа мы отследили IP-адрес этого хопа. Я надеялся, что благодаря их прямой связи с upstream1 получится заставить внести исправления.

Через upstream1 я получил подтверждение, что на указанном мной хопе (первом в upstream2) был внутренний сбой управляющего модуля, который влиял на BGP и маршрутизацию между двумя внутренними сетями. Они перестроили маршрут в обход сбойного устройства и отключили его в ожидании замены.

Фильтр рок-музыки


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

Приложение получало фрагменты аудио, сжимало их с помощью кодека, сжимающего без потерь, а затем отправляло каждый чанк в отдельном UDP-пакете конечной точке. По мере возможности приложение старалось использовать IPv6, потому что это было надёжнее LAN-окружения, хотя при необходимости могло работать и через IPv4.

После бесконечного и нудного поиска причины проблемы, я наконец-то понял, в чём дело. Каким-то образом пользователь задал в сетевом интерфейсе MTU 1200 байтов. А IPv6 не будет автоматически фрагментировать пакеты на уровне IP при MTU ниже 1280 байтов, так что более крупные пакеты просто не смогут отправиться. Поточное приложение будет пытаться отправить аудиопакеты крупнее 1200 байтов, получать ошибку и разрывать соединение.

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

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

Самоисчезающий перебой в работе интернета


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

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

Однажды вечером пропал интернет, но всего на несколько минут. Затем он снова появился, так что я не придал этому значения. Но на следующий день кратковременный перебой повторился, и на третий день тоже. Обычно он происходил около 20 часов, точное время плавало, а иногда его вообще не было. Но каждый раз, когда сеть пропадала, начинал звонить мой внутриобщажный телефон, а людей всё сильнее раздражали эти повторяющиеся перебои.

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

Я постучал, но мне не открыли. Замок был заперт. Но если в комнате нет никого, кто мог бы что-то сделать с компьютером или кабелем, то почему прерывается соединение? И почему оно восстанавливается? На следующий день всё повторилось, мне снова не открыли на стук в дверь. Я решил совсем отключить эту комнату, чтобы у остального этажа работал интернет.

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

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

История Мэла


Настоящие программисты пишут на Фортране


Возможно, сейчас это так, в декадентскую эпоху безалкогольного пива, калькуляторов и user-friendly приложений, но в Старые Добрые Времена, когда термин software звучал забавно, а Настоящие Компьютеры были сделаны из магнитных барабанов и радиоламп, Настоящие Программисты писали на машинном коде. Не на FORTRAN. Не на RATFOR. Даже не на ассемблере. На машинном коде. На настоящих, неприукрашенных, непостижимых шестнадцатеричных числах. Прямо так. Уже несколько поколений программистов выросло, не зная об этом славном прошлом, и я считаю, что должен постараться перекинуть мостик через разрыв поколений и рассказать о том, как Настоящий Программист писал код. Я буду звать его Мэл, потому что так его звали.

Я познакомился с Мэлом, когда устроился в Royal McBee Computer Corp., ныне несуществующую дочернюю компанию производителя печатных машинок. Фирма производила LGP-30 маленький и дешёвый (по сегодняшним меркам) компьютер на барабанной памяти, и только что начала производить RPC-4000, тоже на барабанной памяти, намного улучшенный, более крупный и быстрый. Магнитные сердечники стоили слишком дорого, да они и не выдержали конкуренции (поэтому вы не слышали ни об этой компании, ни о её компьютерах). Меня наняли, чтобы я написал FORFTRAN-компилятор для этого нового чуда, а Мэл был моим проводником по его возможностям. Мэл не одобрял компиляторы. Что хорошего в том, что программа не может переписать собственный код?, спрашивал он. Мэл написал на шестнадцатеричном коде самую популярную программу компании. Она работала на LGP-30 и играла в блэкджек с потенциальными покупателями на компьютерных выставках. Это всегда оказывало драматический эффект. Стенд с LGP-30 выставлялся на каждой выставке, а продавцы IBM собирались вокруг и беседовали друг с другом. Помогало ли это продавать компьютеры? Этот вопрос мы никогда не обсуждали.

В обязанности Мэла входило переписывание блэкджек-программы под RPC-4000. (Портирование? Что это такое?) У нового компьютера была схема адресации один-плюс-один: у каждой машинной инструкции кроме кода операции и адреса нужного операнда был ещё и второй адрес, который показывал, где на вращающемся магнитном барабане записана следующая инструкция. То есть после каждой инструкции шло GO TO! Набейте это в трубку Pascal и выкурите.

Мэл любил RPC-4000, потому что мог оптимизировать свой код: размещать инструкции на барабане так, чтобы как только одна завершалась, вторая сразу оказывалась под считывающей головкой и была готова к немедленному исполнению. Для этого была написана программа, оптимизирующий ассемблер, но Мэл отказывался ею пользоваться. Никогда не знаешь, куда она положит данные, объяснял он, поэтому приходится использовать отдельные константы. Я понял суть этой фразы гораздо позже. Поскольку Мэл знал числовые значения всех операционных кодов и присваивал в барабанной памяти собственные адреса, то каждую написанную им инструкцию можно было считать числовой константой. К примеру, он мог выбрать более раннюю инструкцию сложения и умножить на неё, если она имела подходящее числовое значение. Его код мало кто мог изменять. Я сравнивал вручную оптимизированные Мэлом программы с тем же кодом, который был обработан оптимизирующим ассемблером, и код Мэла всегда исполнялся быстрее. Дело в том, что метод построения архитектуры сверху вниз ещё не изобрели, да Мэл всё равно не пользовался бы им. Сначала он писал внутренние части своих программных циклов, чтобы те первыми получали оптимальные адреса на барабане. А оптимизирующий ассемблер был на такое неспособен. Мэл никогда не писал циклы с задержкой по времени, даже когда неповоротливый Flexowriter требовал делать задержку между выводами символов. Мэл просто размещал инструкции на барабане так, чтобы в момент, когда нужно было считать следующую инструкцию, она проходила мимо считывающей головки, и барабану требовалось сделать ещё один оборот, чтобы её найти. Мэл нашёл для этой процедуры неподражаемый термин. Слово оптимальный (optimum) имеет абсолютное значение, как и уникальный, поэтому в разговорной речи их стали часто делать относительными: не совсем оптимально, или менее оптимально, или не очень оптимально. Мэл называл места на барабане с наибольшим временем задержки самый пессимум (pessimum наихудшие условия среды, переносимые организмом).

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

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

Здравый смысл подсказывал, что это должен быть закрытый цикл, внутри которого циркулирует программа, вечно, бесконечно. Однако программное управление успешно проходило через него и безопасно выходило на другой стороне. У меня ушло две недели, чтобы разобраться в этом. Компьютер RPC-4000 был оснащён современным устройством индексным регистром. Он позволял писать программные циклы, внутри которых использовались индексированные инструкции. При каждом проходе через цикл число из регистра добавлялось к адресу инструкции, чтобы она ссылалась на следующую позицию в серии. Оставалось только инкрементировать индексный регистр при каждом проходе. Мэл этим не воспользовался. Вместо этого он вытягивал инструкцию в машинный регистр, добавлял к её адресу единицу и сохранял обратно. А затем исполнял модифицированную инструкцию прямо из регистра. Цикл был написан с учётом дополнительного времени на исполнение: как только инструкция завершалась, следующая оказывалась под считывающей головкой барабана. Но в цикле не было жульнической проверки. Спасительной подсказкой оказалось то, что был включён бит в индексном регистре он располагался в коде команды между адресом и операционным кодом. Однако Мэл не использовал индексный регистр, оставляя его обнулённым.

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

Исключительно проблема USB


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

Они кричали: Повреждение памяти!. Я думал: Хоспади, исправляйте свои баги. Просматривая дампы падений, мы увидели что это? Программа выполнила запрещённую инструкцию, объединяя две строки с помощью функции из стандартной библиотеки. Хм, странно Следующий журнал: нельзя извлечь страницу из файла подкачки на устройстве, на котором вообще не выделено место для файла подкачки (кажется, я понимаю, почему мы не могли извлечь страницу!).

Когда-то я написал короткую программу. Она выделяла 80 % системной памяти под один массив и записывала в него последовательные целые числа. Затем ждала, чтобы нажали Enter, и проверяла, не изменилось ли содержимое массива. Теперь я загрузил эту программу, подождал 30 секунд, а затем запустил проверку. Никаких проблем. Попытался ещё несколько раз ха, я знал, что никакого повреждения памяти нет! Я выдернул отладочный кабель (USB), секунд через 10 несколько раз быстро вставил и выдернул, затем снова вставил. Бам! 90 ошибок.

Твою же.

Ладно, придётся повозиться с USB-портом. Получается, проблема связана с ним? Не похоже, что драйвер USB реализует волшебный bit fairy-алгоритм, который случайным образом разбрасывает битовые ошибки. Наверное, проблема с железом? Нет, не с ним, но это не помешало нам творить всякие непотребства с USB-портом. Позвали инженеров, которые давно переключились на другой продукт, и теперь они ломали голову над проблемой. Я не помню, сколько времени мы потратили, доказывая себе, что с аппаратной частью был полный, полный, пооооолный порядок. Заземление было в порядке, напряжение стабильное, часы шли точно, а схема DDR-линий была настолько идеальной, что вы заплакали бы от счастья, увидев её.

Устройства, которые тестировались инженерами, становились всё более нестабильными. Я предположил, что аппарат может загрузить данные в память, получить битовые ошибки, а затем выгрузить обратно во флеш-память, возможно даже не в то место (таблица страниц часто повреждалась, так что можно было допустить, что это происходит и со структурами отслеживания файлов. Содержимое могло записываться в неправильные места, а структуры файловой системы могли ломаться, и т.д.) Со временем устройства деградировали настолько, что уже не могли надёжно загрузиться. Наконец один из инженеров сломался и перезаписал в память образ, который лежал у него на ноутбуке. Этот образ был относительно древним.

Чувак. Дело в программном обеспечении.

Что?!?!?! Уверяю, мы не писали bit fairy!

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

Так что это был за волшебный патч? Кто-то добавил в ядро драйвер для чипа, который мы анализировали. Этого чипа в устройстве не было.

Ха! Мы нашли ведьму! СЖЕЧЬ ЕЁ!

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

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

Теперь я был один и наблюдал за происходившей чертовщиной. Начал внимательно анализировать патч. Это был милый файл в 10 тыс. строк на С, который нам предоставил производитель чипа. Описывать его словом хаос было бы слишком снисходительно (справедливости ради, через несколько недель они прислали нам гораздо более продуманный драйвер). Поковырявшись немного, я решил, что в драйвере не было реализовано битовое-жонглирование-ради-забавы. Так в чём же дело? 48 байтов из пяти строк кода. Маленькая структура в загрузочном файле, в которой говорится, по какому адресу шины нужно искать чип. Я удалил большую часть драйвера, но оставил в нём другую структуру. Проблема не исчезла.

Итак, мальчики и девочки, у нас проблема с выравниванием! Каким-то образом эта 48-байтная структура что-то двигает в памяти, и это приводит к ошибкам. Я выяснил, что проблема возникает, когда помещаешь в файл что-нибудь больше 32 и меньше 64 байтов. Это знание не слишком помогло, но хотя бы создавало ощущение прогресса.

Сборка ядра выдавала аккуратный файл System.map. В нём перечислялось, где в виртуальном адресном пространстве ядра находятся все переменные, скомпилированные в ядре. Я выяснил, что моя маленькая структура находится посреди раздела .data. Этот раздел заполнен инициализированными переменными, так что когда бинарник ядра распаковывался в памяти, он записывал все эти переменные из скомпилированного образа. Пользуясь System.map как справочником, я реализовал довольно бестолковый бинарный поиск. По большей части я искал по линкерам разных С-файлов. Я нашёл переменную, с которой нужно было сравнивать; нашёл файл ядра, который её содержит; положил рядом в случайном файле мою волшебную структуру и начал смотреть, проявится ли снова проблема.

Процедура поиска дошла до последних нескольких элементов .data и вернулась с пустыми руками. В памяти с инициализированными переменными не было нужных данных. Полистав дальше файл System.map, я увидел, что не обратил внимания на целый раздел .bss, в который попадали неинициализированные переменные. Наученный прежними ошибками, я сначала проверил начало и конец. Конечно, неинициализированная переменная в начале раздела приводила к ошибкам, а переменная в конце раздела не приводила. Поиск виновника был лишь делом времени. Переменной, чьё перемещение приводило к проблеме, был

Указатель функции?!

Как, чёрт побери, выравнивание указателя функции рушит нашу систему? В архитектуре ARM нельзя читать слова при обращении без выравнивания, то есть каждую 32-битную переменную нужно класть в память по адресу, кратному 4. Указатель функции не исключение, он всегда получает минимальный адрес. Оказывается, в нашей проблемной ситуации адрес был кратен 2n, большей либо равной 64. Любое значение меньше этого порога и проблема пропадала. С выравниванием указателя тоже был порядок.

Не бывает слишком хорошего выравнивания. По крайней мере, не было до возникновения этого бага.

Теперь этот указатель функции не был дедовским указателем. Он ссылался на что-то особенное. В SRAM процессора была область, которую мы могли использовать для связанных с загрузкой задач, если не могли использовать RAM. Чтобы экономить энергию во время простоя, мы скопировали в эту область подпрограмму, задали особый указатель, который ссылался на неё, а потом его вызывали. Что делала подпрограмма? Давайте взглянем на ассемблер. Я не специалист по ARM-ассемблеру, но комментарии были достаточно красноречивы.

// Вычисляет адрес привязанного к памяти управляющего регистра... ... // Теперь выключаем контроллер памяти и переводим LPDDR в режим самоообновления

ЧТО вы делаете?! Быстро же вы перешли от базовых операций с регистрами к отключению контроллера памяти. Я отправил письмо производителю, который написал подпрограмму, и спросил, не упустили ли они что-то.

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

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

В конце концов мы отгрузили надёжный продукт с барьером памяти, и покупателям он понравился. Да, и если вам интересно, я не мог этого заметить с помощью USB-кабеля, потому что мы не могли войти в режим низкого энергопотребления из-за использования USB. Это исключительно проблема USB.

Неверное сообщение об ошибке


В последние часы 17 сентября 1996, за день до запланированного запуска сервиса WebTV, наша группа собралась в эксплуатационном центре в Пало-Альто. Поблизости болталась толпа сисадминов-сетевиков и разработчиков сервисного ПО, чтобы стать свидетелями официального запуска.

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

Несколько человек столпились вокруг, глядя, как он набирает на клавиатуре, ощущая головокружение от предвкушения и недосыпа. Брайс ввёл своё имя, адрес и другую информацию, а затем начал печатать ник. Это было его имя для адреса электронной почты. Он набрал jazz, то есть его почта должна была быть jazz@webtv.net. Когда он нажал Enter на беспроводной клавиатуре, мы услышали характерный звук, обозначающий появление сообщения об ошибке. Все посмотрели на экран.

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

Пользовательские имена сравнивались со списком регулярных выражений, которые позволяли сравнивать с шаблоном. Например, fu.*bar будет сравнивать со всеми именами, начинающимися с fu и заканчивающимися на bar. Если тщательно подобрать паттерны, можно выловить и отклонить такие вопиющие варианты, как shitake и matsushita, в которые встроены ругательства.

Тот же механизм использовался для того, чтобы пользователи не могли выбирать запрещённые имена вроде postmaster, root, admin и help. У нас был такой текстовый файл:

admin.*Имя пользователя не может начинаться с "admin".postmasterВы не postmaster.poopЭто плохое слово.weenieЭто плохое слово.

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

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

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

Полночь. Мы все немного на взводе. Брайс пишет имя, а система отвечает простым сообщением:


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

В это время в другом здании Марк Армстронг (отвечавший за QA) вместе с Брюсом Ликом (одним из основателей компании) сидели перед стойкой из шестнадцати приставок WebTV. Эта стойка, прозванная racksville, была подключена через видеомультиплексор к большому телевизору, одновременно показывающему изображения со всех 16 приставок. Марк и Брюс начали регистрировать приставки, пользуясь клавиатурой с инфракрасным передатчиком. Мы позвонили им по интеркому:

Как у вас идёт?

Всё отлично.

А, хорошо. Вы могли кое-что заметить при регистрации.

Да? Мы не заметили ничего странного.

Заметите.

Ладно. Вводим почтовый индекс пока всё нормально. ОГО!!!

На изображениях со всех 16 приставок возникло дружелюбное сообщение. Начальники предположили, что нам, быть может, стоит поскорее исправить этот глюк. Это показалось нам отличной идеей.

Мы исправили файл, и научили код распознавать и игнорировать пустые строки. Насколько я знаю, WebTV не сказал ни одному клиенту f--k.

Проблема с падением Xbox


В то время команда работала над одной из первых игр для совершенно новой приставки под названием Xbox. Когда финальное тестирование ускорили, QA запустили три приставки из установочной партии, чтобы гонять ночью автоматизированные тесты. Если вчерашняя сборка игры утром всё ещё тестировалась, это говорило о её стабильности.

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

Так началась Охота на Баг. Каждый день ведущие инженеры просматривали имеющиеся улики, выдвигали гипотезы и исключали возможности. Каждую ночь QA получали случайное падение без какой-либо причины. Это невозможно, Как это происходит?, Может, это баг в компиляторе? все самые популярные хиты.

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

К счастью, скоро удалось найти закономерность, хотя и довольно странную. Игра падала только ночью и только на одной из трёх приставок. Начали искать различия между ними. Дело было не в кабеле питания. Не в контроллерах. Не в порядке записи DVD. Переносили приставку на свой стол не падает. Ставили обратно падает. Дело было в специфическом стенде, который использовали QA.

Теперь процесс исключения факторов требовал исключить все переменные. В конце концов, отчаявшись, инженер попытался поменять приставки на столе местами.

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

Ночь тянулась медленно, затем быстро, и наконец наступил рассвет. Игра продолжала работать. Это вдохновляло. Стало подниматься солнце.

И тут наконец-то произошло кое-что интересное: на стол упал луч восходящего солнца. Минута за минутой луч полз по столу к приставкам, его тёплое сияние тихо обволакивало чёрный купол приставки.

Которая быстро упала.

У первых Xbox оказалась проблема: видеокарта могла сбоить, если температура приставки достигала определённого значения. Программное обеспечение было ни при чём. Об аппаратной проблеме сообщили, игру выпустили, а Red Bull заменили на пиво. Ну ладно, будем честны, на виски. Один: ноль в пользу науки.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru