Mother of all bugs

Feb 28, 2007 11:05


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

Я тогда написал некий фичер, который в числе прочего отражался на терминальном интерфейсе наших девайсов (т.е. можно было запустить через терминал некую команду, и получить информацию о различных параметрах этого фичера). Баг был открыт очень крутым тестером по имени Шай, и звучал он просто: при доступе через терминал к этому фичеру, терминал летит (а вместе с ним и весь девайс - прелести embedded OS). Проблема, однако, оказалась в том, что мне не удалось воспроизвести проблему - даже ценой часов, проведенных возле терминала, в то время как Шай воспроизводил ее легко - в том числе и у меня на глазах. В конце концов, я признал свое бессилие - и воспользовался глупейшей стандартнейшей отмазкой: "это явно не в моем коде, попробуй добиться того же самого с другой командой". Каково же было мое удивление, когда через какое-то время Шай сообщил мне, что и с другими (фактически - с любыми) табличными командами (т.е. такими, которые выдают в качестве вывода длинные таблицы) происходит то же самое!

Это уже было удивительно, поскольку означало, что баг - в самой терминальной библиотеке, которую никто в последнее время не менял, и которую все используют очень интенсивно. Тут я уже решил присмотреться внимательно к тому, что именно делает Шай. Обнаружилось вот что: для того, чтобы пролистать таблицу, он нажимает на пробел. Если таблица длинная - нажимает многократно. Затем он нажимает на стрелочку вверх, чтобы достать из истории терминала последнюю команду, затем - энтер. И так много раз подряд: много-много пробелов, стрелочка вверх, энтер. Много-много пробелов, стрелочка вверх, энтер. Далее я заметил, что поскольку он все это делает очень быстро и машинально, иногда он плохо нажимает на стрелочку, и тогда в качестве следующей команды вводится строка пробелов (поскольку пробелов обычно вводится больше, чем нужно для того, чтобы пролистнуть таблицу). Дальше уже было легко: я установил, что баг происходит попросту при вводе строки, содержащей определенное количество пробелов - и больше ничего. Опытным путем я обнаружил, что необходимое для подвисание количество пробелов лежит в диапазоне 16-31.

Мы приближаемся к финишу. Итак, в чем же было дело? Когда терминальная библиотека принимает введеную строку, она помещает ее в преаллоцированный блок памяти, величина которого - наименьшая степень двойки, при которой блок все еще больше строки (в данном случае - 32, поскольку 16-символьная или более строка плюс ноль в конце). Величина блока записывается в его заголовок, с которым оперирует библиотека аллокации (совершенно случайно, величина блока - это последнее поле заголовка). Далее вызывается функция, заносящая строку в историю, которая прежде всего удаляет trailing spaces. Удаляет она их так удачно, что не смотрит, дошла ли до начала строки. Удалив таким образом всю строку (которая, напоминаю, вся состоит из пробелов), она идет еще на один байт назад - и натыкается на поле величины блока в заголовке оного, где находит, о чудо, пробел (32 - это аски код пробела), который радостно затирает. После чего при освобождении данного блока и происходит кирдык, поскольку функция деаллокации воспринимает ноль в поле величины блока как индикацию ошибки.

Вот такие бывают баги, да.

work, programming

Previous post Next post
Up