Это было на самом деле в стороннем компоненте просмотра изображений нашего приложения.
Мы обнаружили, что у 2-3 пользователей нашего приложения компонент просмотра изображений часто выдает исключение и умирает ужасно. Однако у нас были десятки других пользователей, которые никогда не сталкивались с проблемой, несмотря на то, что большую часть рабочего дня мы использовали приложение для той же задачи. Также был один пользователь, который получал это намного чаще, чем остальные.
Мы попробовали обычные шаги:
(1) Если бы они поменяли компьютеры с другим пользователем, у которого никогда не было проблем, чтобы исключить компьютер / конфигурацию. - Проблема последовала за ними.
(2) Заставили их войти в приложение и работать как пользователь, который никогда не видел проблемы. - Проблема все еще следовала за ними.
(3) Имел ли пользователь отчет о том, какое изображение он просматривал, и настроил тестовый жгут, чтобы повторять просмотр этого изображения тысячи раз подряд. Проблема не представилась в ремне безопасности.
(4) Разработчик сидел с пользователями и наблюдал за ними весь день. Они видели ошибки, но не замечали, что они делают что-то необычное, чтобы вызвать их.
Мы боролись с этим в течение нескольких недель, пытаясь выяснить, что общего было у «пользователей с ошибками», чего не было у других пользователей. Я понятия не имею, как, но у разработчика на шаге (4) был момент наступления эврики, чтобы поработать на один день, достойный энциклопедии Брауна.
Он понял, что все «пользователи ошибок» были левшами, и подтвердил этот факт. Только левши получили ошибки, а не правые. Но как левша может вызвать ошибку?
Мы заставили его сесть и снова наблюдать за левшами, обращая особое внимание на то, что они могут делать по-другому, и вот как мы это нашли.
Оказалось, что ошибка возникала только в том случае, если вы перемещали мышь к крайнему правому столбцу пикселей в средстве просмотра изображений, когда оно загружало новое изображение (ошибка переполнения, поскольку у поставщика было вычисление 1 раз для события наведения мыши).
По-видимому, ожидая загрузки следующего изображения, все пользователи естественным образом переместили свою руку (и, следовательно, мышь) к клавиатуре.
Один из пользователей, который чаще всего получал сообщение об ошибке, был одним из тех типов ADD, которые принудительно перемещали свою мышь с нетерпением, ожидая загрузки следующей страницы, таким образом, она намного быстрее перемещала мышь вправо и нажимала кнопку. время точно, поэтому она сделала это, когда произошло событие загрузки. Пока мы не получили исправления от продавца, мы сказали ей просто отпустить мышь после щелчка (следующий документ) и не трогать ее, пока она не загрузится.
Отныне он был известен в легенде в команде разработчиков как «Левша»
Это давным- давно (конец 1980-х годов).
Компания, в которой я работал, написала пакет САПР (на Фортране), который работал на различных рабочих станциях Unix (HP, Sun, Silcon Graphics и т. Д.). Мы использовали наш собственный формат файлов для хранения данных, и когда пакет был запущен, места на диске было мало, поэтому было много битов, используемых для хранения нескольких флагов в заголовках сущностей.
Тип объекта (линия, дуга, текст и т. Д.) Был умножен на 4096 (я думаю) при сохранении. Кроме того, это значение было отменено для обозначения удаленного элемента. Итак, чтобы получить тип, у нас был код, который сделал:
На каждой машине, кроме одной, это дало ± 1 (для линии), ± 2 (для дуги) и т. Д., И мы могли тогда проверить знак, чтобы видеть, был ли удален.
На одной машине (я думаю, HP) у нас была странная проблема, когда обработка удаленных элементов была испорчена.
Это было в дни до IDE и визуальных отладчиков, поэтому мне пришлось вставлять трассировочные операторы и журналы, чтобы попытаться отследить проблему.
В конце концов я обнаружил, что это произошло потому, что в то время как каждый другой производитель реализовал это
MOD
так, в-4096 MOD 4096
результате-1
HP применила его математически правильно, что-4096 MOD 4096
привело к-4097
.В итоге мне пришлось пройти через всю базу кода, сохранив знак значения и сделав его положительным, прежде чем выполнить,
MOD
а затем умножить результат на значение знака.Это заняло несколько дней.
источник
Ух ты, хорошее чтение здесь!
Мои самые тяжелые были годы назад, когда Turbo Pascal был большим, хотя, возможно, это была одна из ранних C ++ IDE того времени. Как единственный разработчик (и третий парень в этом стартапе) я написал что-то вроде упрощенной программы САПР, удобной для продавцов. В то время это было здорово, но развился неприятный случайный сбой. Воспроизвести было невозможно, но случалось достаточно часто, и я отправлялся на охоту за ошибками.
Моя лучшая стратегия заключалась в одном шаге в отладчике. Ошибка возникала только тогда, когда пользователь ввел достаточно чертежа и, возможно, должен был находиться в определенном режиме или в состоянии масштабирования, поэтому было много утомительных установок и очистки точек останова, которые обычно работали в течение минуты, чтобы войти в чертеж, а затем шаг через большой кусок кода. Особенно полезными были точки останова, которые пропускали определенное количество раз, а затем ломались. Все это упражнение пришлось повторить несколько раз.
В конце концов я сузил его до места, где вызывали подпрограмму, получив 2, но изнутри увидел какое-то бессмысленное число. Я мог бы поймать это раньше, но не вошел в эту подпрограмму, предполагая, что она получила то, что ей дали. Ослепленный, предполагая, что самые простые вещи были в порядке!
Оказалось, что в стек вставляется 16-битное int, но подпрограмма ожидает 32-битного. Или что-то типа того. Компилятор не автоматически дополняет все значения до 32 бит или не выполняет достаточную проверку типов. Это было тривиально исправить, просто часть одной строки, вряд ли нужно было думать. Но чтобы добраться туда, понадобилось три дня охоты и допроса очевидного.
Таким образом, у меня есть личный опыт с этим анекдотом о дорогостоящем консультанте, который через некоторое время делает одно прикосновение куда-то и берет 2000 долларов. Руководители требуют разбивки, и это 1 доллар за кран, 1999 долларов за знание, где нажать. За исключением моего случая, это было время, а не деньги.
Извлеченные уроки: 1) использовать лучшие компиляторы, где «лучший» определяется как включающий проверку на столько проблем, сколько компьютерная наука знает, как проверять, и 2) ставить под сомнение простые очевидные вещи или, по крайней мере, проверять их правильное функционирование.
С тех пор все сложные ошибки были действительно трудными, так как я знаю, чтобы проверять простые вещи более тщательно, чем кажется необходимым.
Урок 2 также относится к самой сложной ошибке электроники, которую я когда-либо исправлял, также с тривиальным исправлением, но несколько умных EE были поставлены в тупик на месяцы. Но это не форум по электронике, так что я не буду больше говорить об этом.
источник
Состояние гонки сетевых данных из ада
Я писал сетевой клиент / сервер (Windows XP / C #) для работы с аналогичным приложением на очень старой (Encore 32/77) рабочей станции, написанной другим разработчиком.
По сути, приложение делило / манипулировало определенными данными на хосте, чтобы контролировать процесс хоста, на котором запущена система, с помощью нашего модного интерфейса монитора с сенсорным экраном на базе нескольких ПК.
Он сделал это с 3-х уровневой структурой. Процесс связи считывал / записывал данные на / с хоста, делал все необходимые преобразования формата (порядковый номер, формат с плавающей запятой и т. Д.) И записывал / считывал значения в / из базы данных. База данных действовала как посредник данных между интерфейсами связи и сенсорного экрана. Приложение сенсорного интерфейса генерировало интерфейсы сенсорного экрана в зависимости от того, сколько мониторов было подключено к ПК (это автоматически обнаруживалось).
В заданном временном интервале пакет значений между хостом и нашим компьютером мог передавать только 128 значений максимум по каналу за раз с максимальной задержкой ~ 110 мс на передачу в оба конца (UDP использовался с прямым соединением Ethernet через x-over между Компьютеры). Таким образом, количество разрешенных переменных на основе переменного количества подключенных сенсорных экранов находилось под строгим контролем. Кроме того, хост (хотя и имеющий довольно сложную многопроцессорную архитектуру с шиной совместно используемой памяти, используемой для вычислений в реальном времени) имел вычислительную мощность моего сотового телефона примерно в 1/100, поэтому ему было поручено выполнять как можно меньше обработки, и его сервер / client должен был быть написан на ассемблере, чтобы убедиться в этом (на хосте выполнялась полная симуляция в реальном времени, на которую не могла повлиять наша программа).
Вопрос был. Некоторые значения при изменении на сенсорном экране не будут принимать только что введенное значение, но будут случайным образом переключаться между этим значением и предыдущим значением. Это и только на нескольких определенных значениях на нескольких определенных страницах с определенной комбинацией страниц когда-либо показывало признак. Мы почти полностью пропустили проблему, пока не начали ее прорабатывать в процессе первоначального принятия клиентов
Чтобы определить проблему, я выбрал одно из колеблющихся значений:
Затем я запустил wireshark и начал вручную декодировать захват пакетов. Результат:
Я сто раз перебирал каждую деталь кода связи, не обнаружив ни ошибки, ни ошибки.
Наконец, я начал отправлять электронные письма другому разработчику, подробно спрашивая, как работает его конец, чтобы узнать, что я пропустил. Тогда я нашел это.
Очевидно, что когда он отправлял данные, он не сбрасывал массив данных перед передачей, поэтому, по сути, он просто перезаписывал последний использованный буфер новыми значениями, перезаписывая старые, но старые данные, не перезаписанные, все еще передавались.
Таким образом, если значение было в позиции 80 массива данных, а список запрашиваемых значений изменился до менее 80, но это же значение содержалось в новом списке, тогда оба значения будут существовать в буфере данных для этого конкретного буфера в любом данное время.
Значение, считываемое из базы данных, зависело от временного интервала, когда пользовательский интерфейс запрашивал значение.
Исправление было до боли простым. Считайте количество элементов, поступающих в буфер данных (он фактически содержался как часть протокола пакета), и не считывайте буфер за этим количеством элементов.
Уроки выучены:
Не принимайте современные вычислительные мощности как должное. Было время, когда компьютеры не поддерживали Ethernet, а очистка массива считалась дорогой. Если вы действительно хотите увидеть, как далеко мы продвинулись, представьте систему, которая практически не имеет формы динамического распределения памяти. То есть исполнительный процесс должен был предварительно выделить всю память для всех программ по порядку, и ни одна программа не могла вырасти за эту границу. То есть выделение большего объема памяти программе без перекомпиляции всей системы может привести к серьезному сбою. Интересно, будут ли когда-нибудь люди говорить о днях перед сборкой мусора в одном свете?
При работе в сети с пользовательскими протоколами (или обработке двоичного представления данных в целом) обязательно читайте спецификацию, пока не поймете каждую функцию каждого значения, передаваемого по каналу. Я имею ввиду, читай, пока твои глаза не болят. Люди обрабатывают данные, манипулируя отдельными битами или байтами, имеют очень умные и эффективные способы ведения дел. Отсутствие мельчайших деталей может сломать систему.
Общее время исправления составило 2-3 дня, и большую часть времени я потратил на работу над другими вещами, когда я разочаровался в этом.
SideNote: рассматриваемый хост-компьютер не поддерживает Ethernet по умолчанию. Карта для ее вождения была изготовлена на заказ и модифицирована, а стек протоколов практически не существовал. Разработчик, с которым я работал, был чертовским программистом, он не только реализовал урезанную версию UDP и минимальный поддельный стек Ethernet (процессор не был достаточно мощным для обработки полного стека Ethernet) в системе для этого проекта но он сделал это менее чем за неделю. Он также был одним из руководителей проектных команд, которые изначально разрабатывали и программировали ОС. Скажем так: все, что ему когда-либо приходилось рассказывать о компьютерах / программировании / архитектуре, независимо от того, сколько времени я уже выучил или сколько я уже новичок, я слушал каждое слово.
источник
Фон
Ошибка
Как я это нашел
Сначала я был уверен, что это нормальная проблема с производительностью, поэтому я разработал сложную запись в журнал. Проверял производительность при каждом звонке, общался с базой данных, люди об использовании смотрели на серверы на предмет проблем. 1 неделя
Тогда я был уверен, что у меня возникла проблема с конфликтом потоков. Я проверил мои тупики, попытался создать ситуацию, создать инструменты, чтобы попытаться создать ситуацию в отладке. С растущим разочарованием в управлении я обратился к своим коллегам с советами: от перезапуска проекта с нуля до ограничения сервера одним потоком. 1,5 недели
Затем я посмотрел на блог Тесс Феррандез, создал файл дампа пользователя и аннулировал его с помощью windebug в следующий раз, когда сервер сделал дамп. Обнаружил, что все мои темы застряли в функции dictionary.add.
Длинный короткий один небольшой словарь, который просто отслеживал, в какой журнал записывать ошибки x потоков, не был синхронизирован.
источник
У нас было приложение, которое взаимодействовало с аппаратным устройством, которое в некоторых случаях не могло работать правильно, если устройство было физически отключено до тех пор, пока оно не было подключено и дважды выполнено программный сброс.
Проблема оказалась в том, что приложение, запущенное при запуске, иногда происходило с ошибками при попытке чтения из файловой системы, которая еще не была смонтирована (например, если пользователь настроил его для чтения из тома NFS). При запуске приложение отправляет драйверу несколько ioctl для инициализации устройства, затем считывает параметры конфигурации и отправляет дополнительные ioctl, чтобы привести устройство в правильное состояние.
Ошибка в драйвере приводила к тому, что недопустимое значение записывалось на устройство при выполнении вызова инициализации, но значение перезаписывалось допустимыми данными после выполнения вызовов для перевода устройства в определенное состояние.
Само устройство имеет батарею и будет обнаруживать, если оно потеряло питание от материнской платы, и будет записывать флаг в энергозависимую память, указывающее, что оно потеряло питание, затем оно перейдет в определенное состояние при следующем включении и Инструкция должна быть отправлена, чтобы очистить флаг.
Проблема заключалась в том, что если питание было отключено после отправки ioctl для инициализации устройства (и записи недопустимого значения на устройство), но до того, как могли быть отправлены действительные данные. Когда устройство снова включится, оно увидит, что флаг установлен, и попытается прочитать недействительные данные, которые были отправлены из драйвера из-за неполной инициализации. Это переведет устройство в недопустимое состояние, когда флаг отключения питания был сброшен, но устройство не получит дальнейшие инструкции, пока драйвер не выполнит его повторную инициализацию. Второй сброс будет означать, что устройство не пытается прочитать недействительные данные, которые были сохранены на нем, и получит правильные инструкции по настройке, позволяющие перевести его в правильное состояние (при условии, что приложение, отправляющее ioctls, не переставало работать) ).
В итоге потребовалось около двух недель, чтобы выяснить точный набор обстоятельств, вызвавших проблему.
источник
Для университетского проекта мы писали систему распределенных узлов P2P, которая делит файлы, это поддерживало многоадресную передачу для обнаружения друг друга, множественные кольца узлов и сервер имен, так что узел назначается клиенту.
Написанный на C ++, мы использовали для этого POCO, поскольку он позволяет хорошо программировать IO, Socket и Thread.
Возникли две ошибки, которые раздражали нас и заставляли нас терять много времени, действительно логичный:
Случайно, компьютер разделял свой локальный IP-адрес вместо своего удаленного IP-адреса.
Это привело к тому, что клиенты подключались к узлу на том же ПК, или узлы для подключения к себе.
Как мы это определили? Когда мы улучшили вывод на сервере имен, мы позже обнаружили, когда перезагружали компьютеры, что наш скрипт для определения IP-адреса был неправильным. Случайно, сначала было указано устройство lo вместо устройства eth0 ... Действительно глупо. Так что теперь мы жестко запрограммировали запросить его из eth0, так как он распределяется между всеми университетскими компьютерами ...
А теперь еще более раздражающий:
Случайно поток пакетов будет случайным образом останавливаться.
Когда следующий клиент подключится, он продолжит ...
Это произошло действительно случайно, и поскольку задействовано более одного компьютера, это стало более раздражающим для устранения этой проблемы, университетские компьютеры не позволяют нам запускать Wireshark на них, поэтому нам остается только догадываться, была ли проблема на стороне отправителя или получателя боковая сторона.
Из-за большого количества выходных данных в коде мы просто приняли предположение, что отправка команд проходит нормально, и
это заставляет нас задуматься, в чем же заключается настоящая проблема ... Казалось, что способ опроса POCO неправильный, и вместо этого мы должны проверять наличие доступных символов. на входящей розетке.
Мы предположили, что это работало, поскольку более простые тесты в прототипе с меньшим количеством пакетов не вызывали этой проблемы, поэтому мы просто предположили, что оператор poll работает, но ... это не так. :-(
Уроки выучены:
Не делайте глупых предположений, таких как порядок сетевых устройств.
Фреймворки не всегда делают свою работу (как реализацию, так и документацию) правильно.
Обеспечьте достаточно вывода в коде, если не разрешено, обязательно сохраняйте расширенные детали в файл.
Когда код не был модульным тестом (потому что это слишком сложно), не думайте, что все работает.
источник
Я все еще нахожусь на своей самой трудной охоте на жука. Это один из тех, иногда его там, а иногда и не ошибки. Вот почему я здесь, в 6:10 утра следующего дня.
Задний план:
Охота
Убийство.
Посмертное.
источник
В прошлом семестре мне пришлось исправлять некоторые запутанные вещи, связанные с параллелизмом, но ошибка, которая по-прежнему наиболее важна для меня, была в текстовой игре, которую я писал в сборке PDP-11 для домашнего задания. Он был основан на игре жизни Конвея, и по какой-то странной причине большая часть информации рядом с сеткой постоянно перезаписывалась информацией, которой не должно было быть. Логика также была довольно простой, поэтому она была очень запутанной. Пройдя несколько раз, чтобы обнаружить, что вся логика верна, я вдруг заметил, в чем проблема. Эта вещь:
.
В PDP-11 эта маленькая точка рядом с числом делает ее основанием 10 вместо 8. Это было рядом с числом, которое ограничивало цикл, который должен был быть ограничен сеткой, размер которой был определен с теми же числами, но в основе 8.
Это все еще выделяется для меня, потому что из суммы ущерба, который вызвало такое крошечное 4-пиксельное дополнение. Так какой вывод? Не кодируйте в сборке PDP-11.
источник
Программа основного кадра перестала работать без причины
Я только что отправил это на другой вопрос. Смотрите пост здесь
Это произошло потому, что они установили более новую версию компилятора на основной фрейм.
Обновление 06/11/13: (Исходный ответ был удален OP)
Я унаследовал это основное приложение. Однажды, из чистого синего это перестало работать. Вот и все ... Боже, он просто остановился.
Моя работа состояла в том, чтобы заставить его работать как можно быстрее. Исходный код не изменялся в течение двух лет, но внезапно он просто прекратился. Я попытался скомпилировать код, и он разбился на строке XX. Я посмотрел на строку XX, и я не мог сказать, что заставит линию XX разорваться. Я попросил подробные спецификации для этого приложения, и не было ни одного. Линия XX не была виновником.
Я распечатал код и начал просматривать его сверху вниз. Я начал создавать блок-схему того, что происходило. Код был настолько запутанным, что я едва мог понять его. Я бросил пытаться построить блок-схему. Я боялся вносить изменения, не зная, как это изменение повлияет на остальную часть процесса, тем более что у меня не было подробностей о том, что делало приложение.
Итак, я решил начать с верхней части исходного кода и добавить whitespce и линейные тормоза, чтобы сделать код более читабельным. Я заметил, что в некоторых случаях были условия, которые объединяли AND и OR, и не было четкого различия между тем, какие данные AND и какие данные OR. Поэтому я начал ставить скобки вокруг условий И и ИЛИ, чтобы сделать их более читабельными.
Когда я медленно спускался, убирая это, я периодически сохранял свою работу. Однажды я попытался скомпилировать код, и случилось нечто странное. Ошибка перепрыгнула через исходную строку кода и теперь была еще ниже. Поэтому я продолжил, разграничивая условия «И» и «ИЛИ» паренами. Когда я закончил убирать это, это сработало. Пойди разберись.
Затем я решил посетить операционную мастерскую и спросить их, устанавливали ли они недавно какие-либо новые компоненты на основной раме. Они сказали да, мы недавно обновили компилятор. Хммм.
Оказывается, старый компилятор вычислял выражение слева направо независимо. Новая версия компилятора также оценивает выражения слева направо, но неоднозначный код, означающий, что неясная комбинация AND и OR не может быть решена.
Урок, который я извлек из этого ... ВСЕГДА, ВСЕГДА, ВСЕГДА используйте парены в разделенных И, а также ИЛИ условиях, когда они используются в сочетании друг с другом.
источник
Задний план:
Охота
Убийство.
Посмертное.
gdb
+ мониторинг! Просто нам потребовалось время, чтобы заподозрить диск, а затем определить причину всплесков активности на графике мониторинга ...источник
Самое сложное никогда не погибало, потому что оно никогда не могло быть воспроизведено, кроме как в полной производственной среде при заводской эксплуатации.
Самый безумный, которого я убил:
Чертежи печатают бред!
Я смотрю на код и ничего не вижу. Я вытаскиваю задание из очереди принтера и проверяю, все ли хорошо. (Это было в эпоху дос, PCL5 со встроенным HPGl / 2 - на самом деле, очень хорошо для построения чертежей и без головной боли при создании растрового изображения в ограниченном объеме памяти.) Я направляю его на другой принтер, который должен это понимать, он печатает нормально ,
Откат кода, проблема все еще там.
Наконец я вручную создаю простой файл и отправляю его на принтер - бред. Оказывается, это была не моя ошибка, а сам принтер. Компания по техническому обслуживанию обновила его до последней версии, когда исправляла что-то другое, и в этой последней версии была ошибка. Заставить их понять, что они убрали критическую функциональность и пришлось перенастроить ее на более раннюю версию, было сложнее, чем найти саму ошибку.
Тот, который был еще более неприятным, но так как он был только на моей коробке, я бы не поставил на первое место:
Borland Pascal, код DPMI для работы с некоторыми неподдерживаемыми API. Запустите его, иногда это работало, обычно он шел, пытаясь разобраться с недопустимым указателем. Тем не менее, он никогда не приводил к неверному результату, как вы ожидаете от нажатия на указатель.
Отладка - если бы я пошагово прошел по коду, он всегда работал бы правильно, иначе он был бы таким же нестабильным, как и раньше. Инспекция всегда показывала правильные значения.
Виновник: их было двое.
1) В коде библиотеки Borland была серьезная ошибка: указатели реального режима хранились в переменных-указателях в защищенном режиме. Проблема в том, что большинство указателей реального режима имеют недопустимые адреса сегментов в защищенном режиме, и когда вы пытаетесь скопировать указатель, он загружает его в пару регистров, а затем сохраняет его.
2) Отладчик никогда не скажет ничего о такой некорректной загрузке в одношаговом режиме. Я не знаю, что он делал внутри, но то, что было представлено пользователю, выглядело совершенно правильно. Я подозреваю, что на самом деле он не выполнял инструкцию, а имитировал ее.
источник
Это просто очень простая ошибка, которую я почему-то превратил в кошмар для меня.
Фон: я работал над созданием собственной операционной системы. Отладка очень сложна (все, что вы можете иметь в трассировке, а иногда даже нет)
Ошибка: вместо двухпоточных переключателей в пользовательском режиме, вместо этого будет общий сбой защиты.
Поиск ошибок: я провел, вероятно, неделю или две, пытаясь решить эту проблему. Вставка трассировочных операторов везде. Изучение сгенерированного кода сборки (из GCC). Распечатка каждого значения, которое я мог.
Проблема: где-то в начале поиска ошибок я поместил
hlt
инструкцию в crt0. Crt0 - это в основном то, что загружает пользовательскую программу для использования в операционной системе. Этаhlt
инструкция вызывает GPF при выполнении из пользовательского режима. Я поместил это там и в основном забыл об этом. (изначально проблема заключалась в переполнении буфера или ошибке выделения памяти)Исправление:
hlt
удалите инструкцию :) После удаления все заработало гладко.Что я узнал: пытаясь отладить проблему, не забывайте исправления, которые вы пытаетесь исправить. Делайте регулярные сравнения с последней стабильной версией контроля версий и посмотрите, что вы изменили за последнее время, когда больше ничего не работает
источник