Что мешало найти? Как вы его отслеживали?
Не достаточно близко, чтобы закрыть, но см. также
https://stackoverflow.com/info/175854/what-is-the-funniest-bug-youve-ever-experienced
Что мешало найти? Как вы его отслеживали?
Не достаточно близко, чтобы закрыть, но см. также
https://stackoverflow.com/info/175854/what-is-the-funniest-bug-youve-ever-experienced
Парсер jpeg, работающий на камере наблюдения, который разбивался каждый раз, когда в комнату вошел генеральный директор компании.
100% воспроизводимая ошибка.
Я тебя не люблю!
Вот почему:
Для вас, кто мало знает о JPEG-сжатии, изображение разломано на матрицу небольших блоков, которые затем закодированы с использованием магии и т.д.
Парсер задохнулся, когда генеральный директор вошел в комнату, потому что у него всегда была рубашка с квадратным рисунком на ней, что вызвало некоторый особый случай контрастных и блочных алгоритмов.
Поистине классический.
Это не случилось со мной, но друг рассказал мне об этом.
Ему пришлось отлаживать приложение, которое очень редко срабатывало. Это будет терпеть неудачу только по средам - в сентябре - после 9-го. Да, 362 дня в году, все было в порядке, и три дня в году он сразу же рухнул.
Он отформатировал дату как "среда, 22 сентября 2008", но буфер был одним символом слишком коротким - так что это вызовет проблему, когда у вас была двузначная DOM в день с самым длинным именем в месяц с самым длинным именем.
Это требует знания немного ассемблера Z-8000, который я объясню, когда мы идем.
Я работал над встроенной системой (в ассемблере Z-8000). Другое подразделение компании заключалось в создании другой системы на одной платформе и написало библиотеку функций, которую я также использовал в своем проекте. Ошибка заключалась в том, что каждый раз, когда я вызывал одну функцию, программа разбивалась. Я проверил все свои данные; они были в порядке. Это было ошибкой в библиотеке, за исключением того, что библиотека использовалась (и прекрасно работала) на тысячах сайтов POS по всей стране.
Теперь процессоры Z-8000 имеют 16 16-разрядных регистров, R0, R1, R2... R15, которые также могут быть рассмотрены как 8 32-битных регистров с именем RR0, RR2, RR4..RR14 и т.д. библиотека была написана с нуля, реорганизовав кучу старых библиотек. Он был очень чистым и соответствовал строгим стандартам программирования. В начале каждой функции каждый регистр, который будет использоваться в этой функции, был помещен в стек, чтобы сохранить его значение. Все было аккуратно и аккуратно - они были идеальны.
Тем не менее, я изучил список ассемблера для библиотеки, и я заметил что-то странное в этой функции. В начале функции у него был PUSH RR0/PUSH RR2, а в конце был POP RR2/POP R0, Теперь, если вы этого не сделали, в начале он нажал 4 значения в стеке, но в конце удалил только 3 из них. Это рецепт катастрофы. Там находится неизвестное значение в верхней части стека, где должен быть обратный адрес. Функция не может работать.
Кроме того, могу напомнить вам, что он работает. Его называли тысячи раз в день на тысячах машин. Он не мог НЕ работать.
После некоторой отладки времени (что было нелегко в ассемблере во встроенной системе с инструментами середины 1980-х годов), он всегда терпел бы крах при возврате, потому что плохое значение отправляло его на случайный адрес. Очевидно, мне пришлось отлаживать рабочее приложение, чтобы понять, почему он не подвел.
Хорошо, помните, что библиотека была очень хороша в сохранении значений в регистрах, поэтому, как только вы поместили значение в регистр, он остался там. В R1 было 0000. В ней всегда было 0000, когда эта функция была вызвана. Поэтому ошибка оставила 0000 в стеке. Поэтому, когда возвращаемая функция вернется, он перейдет к адресу 0000, который, как раз, оказался RET, который вытащил следующее значение (правильный адрес возврата) из стека и перепрыгнет к нему. Данные отлично маскировали ошибку.
Конечно, в моем приложении у меня было другое значение в R1, поэтому он просто разбился....
Это было на Linux, но могло произойти практически на любой ОС. Теперь большинство из вас, вероятно, знакомы с API-интерфейсом BSD. Мы счастливо используем его год за годом, и это работает.
Мы работали над широкомасштабным параллельным приложением, в котором было бы открыто множество сокетов. Чтобы проверить его работу, у нас была группа тестирования, которая открывала бы сотни, а иногда и более тысячи соединений для передачи данных. С самыми высокими номерами каналов наше приложение начнет показывать странное поведение. Иногда он просто разбился. В другой раз мы получили ошибки, которые просто не могли быть правдой (например, accept() возвращает один и тот же дескриптор файла при последующих вызовах, что, конечно, привело к хаосу.)
В файлах журналов мы могли видеть, что что-то пошло не так, но было безумно сложно определить. Тесты с Rational Purify говорят, что ничего не случилось. Но что-то было не так. Мы работали над этим в течение нескольких дней и все больше расстраивались. Это был showblocker, потому что уже согласованный тест может вызвать хаос в приложении.
Поскольку ошибка произошла только в ситуациях с высокой нагрузкой, я дважды проверял все, что мы делали с сокетами. Мы никогда не тестировали случаи высокой нагрузки в Purify, потому что это было невозможно в такой ситуации с интенсивной памятью.
Наконец, и, к счастью, я вспомнил, что массивное количество сокетов может быть проблемой с select(), который ждет изменений состояния в сокетах (может читать/писать/ошибка). Разумеется, наше приложение начало наносить вред именно в тот момент, когда оно достигло сокета с дескриптором 1025. Проблема в том, что select() работает с параметрами битового поля. Поля бит заполняются макросами FD_SET() и друзьями, которые НЕ ПРОВЕРЯЮТ ИХ ПАРАМЕТРЫ ДЛЯ ДЕЙСТВИТЕЛЬНОСТИ.
Поэтому каждый раз, когда мы получаем более 1024 дескрипторов (каждая ОС имеет свой собственный предел, ядра Linux ванили имеют 1024, фактическое значение определяется как FD_SETSIZE), макрос FD_SET с радостью перезаписывает свое поле бит и записывает мусор в следующую структуру в Память.
Я заменил все вызовы select() на poll(), который является хорошо продуманной альтернативой вызову arcane select(), и ситуации с высокой нагрузкой никогда не были проблемой в будущем. Нам повезло, потому что вся обработка сокетов была в одном классе инфраструктуры, где 15 минут работы могли решить проблему. Было бы намного хуже, если бы вызовы select() были разбросаны по всему коду.
Извлеченные уроки:
даже если функции API 25 лет, и все используют ее, у нее могут быть темные углы, которые вы еще не знаете
Неконтролируемые записи в макросах API - это EVIL
инструмент отладки, такой как Purify, не может помочь во всех ситуациях, особенно когда используется большая часть памяти
У вас всегда есть рамки для вашего приложения, если это возможно. Использование его не только повышает переносимость, но и помогает в случае ошибок API
многие приложения используют select(), не думая о пределе сокета. Поэтому я уверен, что вы можете вызвать ошибки в много популярных программ, просто используя множество сокетов. К счастью, в большинстве приложений никогда не будет более 1024 сокетов.
Вместо того, чтобы иметь безопасный API, разработчики ОС любят возлагать вину на разработчика. На странице man select() выберите
"Поведение этих макросов undefined, если значение дескриптора равно меньше нуля или больше или равный FD_SETSIZE, который обычно по крайней мере, равное максимальному числу дескрипторов, поддерживаемых Система".
Это вводит в заблуждение. Linux может открыть более 1024 сокетов. И поведение абсолютно четко определено: использование неожиданных значений приведет к разрушению запущенного приложения. Вместо того, чтобы сделать макросы устойчивыми к незаконным значениям, разработчики просто перезаписывают другие структуры. FD_SET реализуется как встроенная сборка (!) В заголовках linux и будет оценивать одну команду ввода ассемблера. Ни в коем случае ни малейшая проверка проверок.
Чтобы протестировать собственное приложение, вы можете искусственно раздуть количество дескрипторов, используемых программным путем открывать файлы или сокеты FD_SETSIZE непосредственно после main(), а затем запускать ваше приложение.
Thorsten79
Шахта была аппаратной проблемой...
В тот же день я использовал DEC VaxStation с большим 21-дюймовым монитором CRT. Мы перешли в лабораторию в нашем новом здании и установили две VaxStations в противоположных углах комнаты. После включения питания мой монитор мерцал как дискотека (да, это были 80-е годы), но другой монитор не сделал.
Хорошо, поменяйте мониторы. Другой монитор (теперь подключенный к моему VaxStation) мерцал, и мой прежний монитор (переместился через комнату) не сделал.
Я помнил, что мониторы на основе ЭЛТ были восприимчивы к магнитным полям. Фактически, они были очень восприимчивы к переменным магнитным полям 60 Гц. Я сразу же заподозрил, что что-то в моей рабочей области генерирует изменяющееся магнитное поле на 60 Гц.
Сначала я подозревал что-то в своей рабочей области. К сожалению, монитор все еще мерцал, даже когда все другое оборудование было выключено и отключено от сети. В этот момент я начал подозревать что-то в этом здании.
Чтобы протестировать эту теорию, мы превратили VaxStation и его монитор на 85 фунтов в портативную систему. Мы поместили всю систему на ротационную тележку и подключили ее к 100-футовому оранжевому удлинительному шнуру. План состоял в том, чтобы использовать эту установку в качестве переносного измерителя напряженности поля, чтобы найти неисправную часть оборудования.
Перемещение монитора вокруг нас полностью смутило. Монитор мерцал ровно в половине комнаты, но не с другой стороны. Комната была в форме квадрата, с дверями в противоположных углах, и монитор мерцал на одной стороне линии диагноза, соединяющей двери, но не с другой стороны. Комната была окружена на всех четырех сторонах коридорами. Мы вытащили монитор в коридоры, и мерцание остановилось. Фактически, мы обнаружили, что мерцание произошло только в одной треугольной половине комнаты, и нигде больше.
После периода полной путаницы я вспомнил, что в комнате была двухполосная система потолочного освещения с переключателями света на каждой двери. В тот момент я понял, что не так.
Я переместил монитор в половину комнаты с проблемой и выключил потолочные светильники. Мерцание остановилось. Когда я включил свет, мерцание возобновилось. Включение и выключение света из любого переключателя освещения включало или выключало мерцание в половине комнаты.
Проблема была вызвана тем, что кто-то отрезал углы, когда они подключили потолочные светильники. При подключении двухпозиционного переключателя на цепи освещения вы запускаете пару проводов между контактами переключателя SPDT и один провод от общего на одном коммутаторе, через лампы и до общего на другом коммутаторе.
Обычно эти провода соединены вместе. Они выходят из группы из одной коммутационной коробки, запускаются в верхний потолочный светильник и в другую коробку. Основная идея заключается в том, что все токопроводящие провода соединены вместе.
Когда здание было подключено, один провод между переключателями и светом был проложен через потолок, но провода, проходящие между переключателями, были проложены через стены.
Если все провода проходили близко друг к другу и параллельно друг другу, тогда магнитное поле, генерируемое током в одном проводе, было отменено магнитным полем, генерируемым равным и противоположным током в соседнем проводе. К сожалению, способ, которым были на самом деле подключены огни, означал, что одна половина комнаты была в основном внутри большого трансформатора с одним поворотным трансформатором. Когда свет горел, ток течет в петле, а плохой монитор в основном сидит внутри большого электромагнита.
Мораль истории: горячие и нейтральные линии в электросети переменного тока находятся рядом друг с другом по уважительной причине.
Теперь все, что я должен был сделать, это объяснить руководству, почему они должны были переделать часть своего нового здания...
Ошибка, с которой вы сталкиваетесь с каким-то кодом, и, изучив ее, вы заключаете: "Там никоим образом не могло быть!" и внезапно он перестает работать, хотя он всегда работал раньше.
Один из продуктов, которые я помогал в моей работе, работал на клиентском сайте несколько месяцев, собирая и счастливо записывая каждое событие, полученное в базу данных SQL Server. Он работал очень хорошо около 6 месяцев, собирая около 35 миллионов записей или около того.
Затем однажды наш клиент спросил нас, почему база данных не обновлялась почти две недели. При дальнейшем исследовании мы обнаружили, что соединение с базой данных, которое выполняло вставки, не удалось вернуться из вызова ODBC. К счастью, поток, который делает запись, был отделен от остальных потоков, позволяя всем, кроме потока записи, продолжать функционировать правильно в течение почти двух недель!
Мы несколько недель подряд пытались воспроизвести проблему на любой другой машине, кроме этой. Мы никогда не смогли воспроизвести проблему. К сожалению, некоторые из наших других продуктов затем начали сбой примерно таким же образом, ни один из которых не разделяет их потоки базы данных от остальной части их функциональности, в результате чего все приложение зависает, которое затем должно было перезапускаться вручную каждый раз, когда они разбился.
Недели исследования прошли через несколько месяцев, и у нас все еще были те же симптомы: полные блокировки ODBC в любом приложении, которые мы использовали в базе данных. К этому времени наши продукты пронизаны отладочной информацией и способами определения того, что пошло не так, и где, даже до такой степени, что некоторые из продуктов обнаружат тупик, собирают информацию, отправляют нам результаты, а затем перезапускают себя.
Во время работы на сервере один день, все еще собирая отладочную информацию из приложений, когда они разбились, пытаясь выяснить, что происходит, сервер BSoD на мне. Когда сервер вернулся в сеть, я открыл мини-накопитель в WinDbg, чтобы выяснить, что это за нарушитель. Я получил имя файла и проследил его до фактического файла. Изучив информацию о версии в файле, я понял, что это часть антивирусного пакета McAfee, установленного на компьютере.
Мы отключили антивирус и не имели ни одной проблемы с тех пор.
Я просто хочу указать на довольно распространенную и неприятную ошибку, которая может произойти в это время google-области:
вставки кода и печально известного минуса
То есть, когда вы скопируете какой-нибудь код с minus, вместо обычного символа ASCII, минус ('-').
Плюс, минус (U + 2212), Hyphen-Minus (U + 002D)
Теперь, хотя минус предположительно отображается дольше, чем дефис-минус, на некоторых редакторах (или на окнах оболочки DOS), в зависимости от используемой кодировки, он фактически отображается как обычный '-' минус.
И... вы можете потратить часы, пытаясь понять, почему этот код не компилируется, удаляя каждую строку один за другим, пока вы не найдете фактическую причину!
Может быть, это не самая сложная ошибка, но достаточно расстраивающаяся;)
(Спасибо ShreevatsaR за определение инверсии в моем исходном сообщении - см. комментарии)
Во-первых, наш выпущенный продукт обнаружил ошибку, но когда я попытался отладить проблему, это не произошло. Я думал, что сначала это было "релиз против debug", но даже когда я скомпилировал код в режиме выпуска, я не смог воспроизвести проблему. Я пошел посмотреть, сможет ли какой-нибудь другой разработчик воспроизвести проблему. Неа. После многократного расследования (создания смешанного кода сборки/кода кода C) выхода программы и перехода через код сборки выпущенного продукта (yuck!), Я нашел строку нарушения. Но линия выглядела просто отлично! Затем мне пришлось искать то, что сделали инструкции по сборке - и, конечно же, неправильная инструкция сборки была в выпущенном исполняемом файле. Затем я проверил исполняемый файл, который создал моя среда сборки, - он имел правильную инструкцию сборки. Оказалось, что сборщик каким-то образом получил коррумпированность и создал плохой код сборки только для одной инструкции для этого приложения. Все остальное (включая предыдущие версии нашего продукта) выдало идентичный код другим машинам разработчиков. После того, как я показал свое исследование менеджеру программного обеспечения, мы быстро перестроили нашу машину сборки.
Где-то глубоко в недрах сетевого приложения была строка (упрощенная):
if (socket = accept() == 0)
return false;
//code using the socket()
Что произошло, когда вызов преуспел? socket
было установлено равным 1. Что делает send()
, когда задано 1? (например, в:
send(socket, "mystring", 7);
Он печатает до stdout
... это я нашел через 4 часа, задаваясь вопросом, почему, при отключении моего printf()
, мое приложение печаталось в окне терминала вместо отправки данных по сети.
С FORTRAN на миникомпьютере Data General в 80 мы имели случай, когда компилятор заставил константу 1 (один) обрабатываться как 0 (ноль). Это произошло потому, что какой-то старый код передавал константу значения 1 функции, которая объявила переменную как параметр FORTRAN, что означало, что она была (должна быть) неизменной. Из-за дефекта кода мы выполнили назначение переменной параметра, и компилятор с радостью изменил данные в ячейке памяти, которую он использовал для константы 1 до 0.
Многие несвязанные функции позже у нас был код, который сравнивал с литеральным значением 1, и тест потерпел неудачу. Я помню, как долго смотрел этот код в отладчике. Я бы распечатал значение переменной, было бы еще 1 тест "if (foo.EQ. 1)". Мне потребовалось много времени, прежде чем я подумал попросить отладчика распечатать то, что считал значением 1. Затем он взял много волосков, чтобы проследить код, чтобы найти, когда константа 1 стала 0.
Не очень жесткий, но я много рассмеялся, когда он был раскрыт.
Когда я поддерживал систему обработки заказов 24/7 для интернет-магазина, клиент жаловался, что его заказ был "усечен". Он утверждал, что, хотя в заказе, который он размещал, фактически содержалось N позиций, система принимала гораздо меньше позиций без каких-либо предупреждений.
После того, как мы проследили поток порядка через систему, были обнаружены следующие факты. Для хранения элементов заказа в базе данных хранилась процедура. Он принял список элементов заказа как строку, которая закодировала список (product-id, quantity, price)
, как это:
"< 12345, 3, 19.99 > < 56452, 1, 8.99 > < 26586, 2, 12.99 > "
Теперь автор хранимой процедуры был слишком умным, чтобы прибегать к чему-то вроде обычного синтаксического анализа и циклирования. Поэтому он напрямую преобразовал строку в инструкцию SQL-multi-insert, заменив "<"
на "insert into ... values ("
и ">"
на ");"
. Который был прекрасен и денди, если только он не сохранил результирующую строку в переменной varchar (8000)!
Случилось так, что его "insert ...; insert ...;"
был усечен на 8000-м символе, и для этого конкретного порядка сокращение было "счастливым" достаточно, чтобы произойти прямо между insert
s, так что усеченный SQL оставался синтаксически правильным.
Позже я узнал, что автор sp был моим боссом.
У меня была ошибка в консольной игре, которая произошла только после того, как вы воевали и выиграли длительную битву босса, а затем только примерно 1 раз в 5. Когда она срабатывала, это оставило бы оборудование 100% вклинило и не могло говорить к внешнему миру вообще.
Это была самая короткая ошибка, с которой я когда-либо сталкивался; модификация, автоматизация, инструментарий или отладка битвы босса скрыть ошибку (и, конечно, мне пришлось бы сделать 10-20 прогонов, чтобы определить, что ошибка скрыта).
В конце я обнаружил проблему (кеш/DMA/прерывание), прочитав код снова и снова в течение 2-3 дней.
При тестировании некоторых новых функций, которые я недавно добавил в торговое приложение, я заметил, что код для отображения результатов определенного типа торговли никогда не будет работать должным образом. Посмотрев на систему управления источниками, было очевидно, что эта ошибка существовала как минимум год, и я был поражен тем, что никто из трейдеров не заметил ее.
После некоторого недоумения и проверки с коллегой я исправил ошибку и продолжил тестирование моих новых функций. Около 3 минут спустя зазвонил мой телефон. На другом конце линии был сердитый трейдер, который жаловался, что одна из его профессий не показывалась правильно.
После дальнейших исследований я понял, что трейдер был поражен той же ошибкой, которую я заметил в коде 3 минуты назад. Эта ошибка лежала около года, просто ожидая, когда разработчик придет и обнаружит ее, чтобы она могла нанести реальный удар.
Это хороший пример типа ошибки, называемой Schroedinbug. Хотя большинство из нас слышало об этих необычных сущностях, это жуткое чувство, когда вы на самом деле сталкиваетесь с одним в дикой природе.
Это снова, когда я думал, что С++ и цифровые часы были довольно аккуратными...
Я получил репутацию за то, что смог решить сложные утечки памяти. У другой команды была утечка, которую они не могли отследить. Они попросили меня расследовать.
В этом случае они были COM-объектами. В основе системы был компонент, который выдавал много извилистых небольших COM-объектов, которые все выглядели более или менее одинаково. Каждый из них был раздан различным клиентам, каждый из которых отвечал за то, что он выполнял AddRef()
и Release()
столько же раз.
Невозможно автоматически вычислить, кто вызвал каждый AddRef
, и имели ли они Release
d.
Я провел несколько дней в отладчике, записывая шестнадцатеричные адреса на маленьких листах. Мой кабинет был покрыт ими. Наконец я нашел преступника. Команда, которая попросила меня о помощи, была очень благодарна.
На следующий день я перешел на язык GC'd. *
(* На самом деле это не правда, но будет хорошим окончанием истории.)
Брайан Кэнтрилл из Sun Microsystems дал отличную технику Google Tech по ошибке, которую он обнаружил с помощью инструмента, который он помог разработать под названием dtrace.
The Tech Talk - забавный, вызывающий, информативный и очень впечатляющий (и длинный, около 78 минут).
Я не буду давать никаких спойлеров здесь, в чем была ошибка, но он начинает раскрывать виновника примерно в 53:00.
Два самых сложных ошибок, которые приходят на ум, были в одном типе программного обеспечения, только один был в веб-версии и один в версии Windows.
Этот продукт представляет собой просмотрщик/редактор плана. Веб-версия имеет флэш-интерфейс, который загружает данные в виде SVG. Теперь это работает нормально, только иногда браузер зависает. Только на нескольких рисунках, и только когда вы немного пошевелили мышью над рисунком. Я сузил проблему до одного слоя чертежа, содержащего 1,5 МБ данных SVG. Если бы я взял только подраздел данных, любой подраздел, зависание не произошло. В конце концов мне стало ясно, что проблема, вероятно, заключалась в том, что в файле было несколько разных разделов, которые в совокупности вызвали ошибку. Разумеется, после случайного удаления разделов слоя и тестирования на ошибку я обнаружил оскорбительную комбинацию инструкций рисования. Я написал обходной путь в генераторе SVG, и ошибка была исправлена без изменения строки actionscript.
В том же продукте со стороны окон, написанном в Delphi, у нас была сопоставимая проблема. Здесь продукт берет файлы autocad DXF, импортирует их во внутренний формат чертежа и отображает их в пользовательском графическом движке. Эта процедура импорта не особенно эффективна (она использует много копий подстроки), но она выполняет свою работу. Только в этом случае это не так. Файл размером 5 мегабайт обычно импортируется через 20 секунд, но в одном файле это заняло 20 минут, поскольку объем памяти увеличился до гигабайта или более. Сначала это казалось типичной утечкой памяти, но инструменты утечки памяти сообщили об этом, и ручная проверка кода тоже ничего не обнаружила. Проблема оказалась ошибкой в распределителе памяти Delphi 5. В некоторых условиях, которые этот конкретный файл был должным образом воссоздан, он был бы подвержен серьезной фрагментации памяти. Система будет пытаться выделить большие строки и не найти нигде для их размещения, кроме выше всего выделенного блока памяти. Интеграция новой библиотеки распределения памяти исправила ошибку, не меняя строку кода импорта.
Возвращаясь назад, наиболее сложными ошибками являются те, чье исправление включает в себя изменение другой части системы, чем та, где возникает проблема.
Когда клиентский кролик-кролик грызет частично через кабель Ethernet. Да. Это было плохо.
Была ошибка на платформе с очень плохой отладчиком устройства. Мы получили бы сбой на устройстве , если бы мы добавили printf в код. Затем он разбился бы в другом месте, чем местоположение printf. Если мы переместим printf, авария будет перемещаться или исчезать эфир. На самом деле, если бы мы изменили этот код, изменив некоторые простые утверждения, произошел сбой, в котором не было бы никакого отношения к коду, который мы действительно изменили.
Оказывается, в репозитории для нашей платформы была ошибка . репозиторий не был нулевым, инициализируя раздел ZI, а используя таблицу перемещения для инициализации значений. Таким образом, в любое время, когда таблица перемещений изменилась в двоичном формате, ошибка изменится. Таким образом, просто добавленный printf изменит таблицу перемещений и там для ошибки.
Это случилось со мной в то время, когда я работал в компьютерном магазине.
Один клиент пришел однажды в магазин и сказал нам, что его новый компьютер отлично работал по вечерам и ночью, но он не работает вообще в полдень или поздно утром. Проблема заключалась в том, что указатель мыши не двигается в это время.
Первое, что мы сделали, это изменить свою мышь на новую, но проблема не была исправлена. Разумеется, обе мыши работали в магазине без ошибок.
После нескольких попыток мы обнаружили, что проблема связана с этим брендом и моделью мыши. Клиентская рабочая станция была близка к очень большому окну, а в полдень мышь находилась под прямым солнечным светом. Его пластик был настолько тонким, что при таких обстоятельствах он стал полупрозрачным, а солнечный свет предотвращал использование оптико-механического колеса: |
Моя команда унаследовала CGI-многопоточное веб-приложение С++. Основной платформой была Windows; далекой, вторичной платформой была Solaris с потоками Posix. По какой-то причине стабильность в Solaris была катастрофой. У нас были разные люди, которые рассматривали проблему уже более года, в выключенном состоянии (в основном выключено), в то время как наш торговый персонал успешно продвинул версию Windows.
Симптомом была жалкая стабильность: широкий спектр системных сбоев с небольшим рифмом или причиной. В приложении использовались как Corba, так и домашний протокол. Один разработчик зашел так далеко, что удалил всю подсистему Corba как отчаянную меру: не повезло.
Наконец, старший, оригинальный разработчик вслух задумался над идеей. Мы рассмотрели его и, в конце концов, нашли проблему: в Solaris был параметр времени компиляции (или времени выполнения?) Для настройки размера стека для исполняемого файла. Он был установлен неправильно: слишком маленький. Таким образом, в приложении закончились стеки и трассировки стека, которые были полными красными сельдевыми.
Это был настоящий кошмар.
Извлеченные уроки:
Сообщение Адама Лисса, рассказывающее о проекте, над которым мы работали, напомнило мне интересную ошибку, с которой мне пришлось иметь дело. На самом деле это не ошибка, но мы займемся этим через минуту.
Исполнительное резюме приложения в случае, если вы еще не видели сообщение Адама: программное обеспечение автоматизации продаж... на ноутбуках... в конце дня, когда они набираются... для синхронизации с базой данных Mother.
Один пользователь жаловался, что каждый раз, когда он пытался набрать номер, приложение рушится. Пользователи поддержки клиентов прошли через все свои обычные диагностические трюки, и они ничего не нашли. Таким образом, они должны были относиться к конечной: иметь пользователя FedEx ноутбук в наших офисах. (Это было очень много, так как каждая локальная база данных для ноутбуков была настроена для пользователя, поэтому новый ноутбук должен был быть подготовлен, отправлен пользователю для его использования, пока мы работали над его оригиналом, тогда нам пришлось поменяться местами и попросите его, наконец, синхронизировать данные с первым оригинальным ноутбуком).
Итак, когда пришел ноутбук, мне было дано понять проблему. Теперь синхронизация включала подключение телефонной линии к внутреннему модему, переход на страницу "Связь" нашего приложения и выбор номера телефона из раскрывающегося списка (с последним номером, который был выбран заранее). Числа в DDL были частью настройки, и в основном это был номер офиса, номер офиса с префиксом "+1", номер офиса с префиксом "9", "если они были звоните из отеля и т.д.
Итак, я нажимаю значок "COMM" и нажимаю return. Он набрал номер, он подключился к модему, а затем сразу же разбился. Я устал еще пару раз. 100% повторяемость.
Итак, зацепил область данных между ноутбуком и телефонной линией и просмотрел данные, проходящие через линию. Это выглядело довольно странно... Самое странное, что я мог его прочитать!
Пользователь, по-видимому, хотел использовать свой ноутбук для набора в локальную BBS-систему, поэтому измените конфигурацию приложения, чтобы использовать номер телефона BBS, а не компанию. Наше приложение ожидало нашего проприетарного бинарного протокола - недолговечные потоки текста ASCII. Буферы переполнены - KaBoom!
Тот факт, что вызов, набранный при запуске сразу после того, как он изменил номер телефона, может дать среднему пользователю понять, что это было причиной проблемы, но этот парень никогда не упоминал об этом.
Я установил номер телефона и отправил его обратно в службу поддержки, отметив, что парень выбрал "Bonehead user of the week". (*)
(*) OkOkOk... Вероятно, очень хорошая вероятность того, что на самом деле произошло то, что парень, увидев, что его отец набирал каждую ночь, понял, что как вы набираете номер BBS, и когда-то менял номер телефона, когда он был дома один с ноутбуком. Когда он разбился, он не хотел признавать, что он коснулся ноутбука, не говоря уже о его сломании; поэтому он просто убрал его и никому не сказал.
Это было во время моей дипломной работы. Я писал программу для моделирования эффекта высокоинтенсивного лазера на атоме гелия с использованием FORTRAN.
Один тестовый прогон работал следующим образом:
Они должны быть постоянными в целом, но они не были. Они делали всевозможные странные вещи.
После отладки в течение двух недель я стал неистовым при регистрации и зарегистрировал каждую переменную на каждом этапе моделирования, включая константы.
Таким образом, я узнал, что я написал над концом массива, который изменил константу!
Друг сказал, что он однажды изменил буквальный 2 с такой ошибкой.
Тупик в моей первой многопоточной программе!
Было очень сложно найти его, потому что это произошло в пуле потоков. Иногда нить в пуле зашла бы в тупик, а остальные все равно работали бы. Поскольку размер пула был намного больше необходимого, потребовалось неделю или две, чтобы заметить первый симптом: приложение полностью висело.
Я потратил несколько часов на несколько дней, отлаживая ряд вещей, которые в конечном итоге были исправлены буквально всего несколькими символами.
Некоторые примеры:
У ffmpeg есть эта неприятная привычка вызывать предупреждение о "обрезке мозгового флага" (ссылаясь на случай, когда значения обрезки в потоке равны >= 16), когда значения урожая в потоке были фактически совершенно допустимыми. Я исправил это, добавив три символа: "h → ".
x264 имел ошибку, в которой в крайне редких случаях (один в миллионе кадров) с определенными опциями он создавал бы случайный блок совершенно неправильного цвета. Я исправил ошибку, добавив букву "O" в двух местах в коде. Оказалось, что я неправильно использовал имя #define в более раннем фиксации.
Моя первая "настоящая" работа была для компании, которая написала программное обеспечение для автоматизации продаж клиент-сервер. Наши клиенты запускали клиентское приложение на своих (15-фунтовых) ноутбуках, и в конце концов они набрали до наших серверов unix для синхронизации с базой данных Mother. После ряда жалоб мы обнаружили, что астрономическое число вызовов снижалось в самом начале, во время аутентификации.
После нескольких недель отладки мы обнаружили, что аутентификация всегда была неудачной, если на входящий вызов был дан ответ на процесс getty на сервере, чей идентификатор процесса содержал четное число, за которым сразу последовал 9. Оказывается, аутентификация была домашней схемой, которая зависел от 8-символьного строкового представления PID; ошибка вызвала оскорбительный PID для сбоя getty, который обновлялся с новым PID. Второй или третий вызов обычно нашел приемлемый PID, и автоматический повторный набор сделал ненужным для клиентов вмешательство, поэтому это не считалось серьезной проблемой, пока телефонные счета не пришли в конце месяца.
"Исправить" (ах) было преобразование ПИД-кода в строку, представляющую ее значение в восьмеричном, а не в десятичном значении, что делает невозможным содержать 9 и ненужным для устранения основной проблемы.
В принципе, все, что связано с потоками.
Я занимал должность в компании, когда у меня было сомнительное различие в том, что я один из тех, кто достаточно удобен для потоковой обработки, чтобы отлаживать неприятные проблемы. Ужас. Вам нужно будет получить какой-то сертификат, прежде чем вам будет разрешено писать потоковый код.
Я слышал о классической ошибке в старшей школе; терминал, на который вы могли бы войти, только если вы сидели в кресле перед ним. (Если бы вы стояли, он отклонил бы ваш пароль.)
Он воспроизводился довольно надежно для большинства людей; вы могли бы сидеть в кресле, войти в систему, выйти из системы... но если вы встанете, вам откажут каждый раз.
В конце концов, оказалось, что какой-то рывок заменил пару соседних клавиш на клавиатуре, E/R и C/V IIRC, и когда вы сели, вы коснулись и вошли, но когда вы встали, для поиска "n peck", поэтому вы посмотрели на неработающие ярлыки и не смогли.
В то время как я не помню конкретный экземпляр, самая жесткая категория - это те ошибки, которые появляются только после того, как система работает в течение нескольких часов или дней, а когда она идет вниз, оставляет мало или вообще не следит за тем, что вызвало крах. Что делает их особенно плохими, так это то, что независимо от того, насколько хорошо вы думаете, что вы обосновали причину, и применили соответствующее исправление, чтобы исправить это, вам придется подождать еще несколько часов или дней, чтобы получить уверенность в том, что вы он действительно прибил его.
Наш сетевой интерфейс, банкоматная карта с поддержкой DMA, изредка будет предоставлять поврежденные данные в принимаемых пакетах. CRC AAL5 был проверен как правильный, когда пакет вышел из провода, но данные DMAd в память были бы неправильными. Контрольная сумма TCP, как правило, ложится на нее, но назад в пьянящие дни ATM люди были в восторге от запуска собственных приложений непосредственно на AAL5, полностью отказавшись от TCP/IP. В конце концов мы заметили, что коррупция произошла только на некоторых моделях рабочей станции поставщика (которые останутся безымянными), а не другие.
Вычисляя CRC в программном обеспечении драйвера, мы смогли обнаружить поврежденные пакеты за счет огромного выигрыша производительности. При попытке отладки мы заметили, что если бы мы только что сохранили пакет на некоторое время и вернулись, чтобы посмотреть на него позже, повреждение данных вызвало бы магическое излечение. Содержимое пакета было бы в порядке, и если бы драйвер вычислил CRC второй раз, он проверит ok.
Мы обнаружили ошибку в кэше данных процессора доставки. Кэш в этом процессоре не был согласован с DMA, требуя, чтобы программное обеспечение явно скрывало его в надлежащее время. Ошибка заключалась в том, что иногда кеш фактически не очищал его содержимое, когда ему было сказано сделать это.