Баг Konsole повержен !

Written by elwood

Вчера ночью я наконец добил этот баг, а сегодня вечером патч уже был закоммичен в master git-репозитория konsole. Для того, чтобы разобраться в сути проблемы, понадобилось несколько вечеров, пара виртуалок с линуксами OpenSuse (здесь, собственно, баг был обнаружен) и Debian (на ней я работал с KDevelop, потому что мне показалось, что gdb будет работать лучше в стабильной версии KDevelop – на самом деле всё было так же как и в OpenSuse) и исходники konsole и xterm.

Поиск точки сбоя

Как известно, чтобы поправить что-то, нужно сначала разобраться, что именно работает не так, локализовать проблему. Предстояло определить, что служит причиной неверного вывода на экран. Исходники konsole написаны хорошо, с большим количеством комментариев и удачными названиями классов, поэтому кандидаты на пристальный осмотр были определены сразу: классы TerminalDisplay, Emulation, Vt102Emulation, Pty и Screen. Идея была следующая: написать дампер текущего состояния визуализации и вызывать его при каждом обновлении. Обновление производил TerminalDisplay в функции updateImage(), и я добавил в него дамп матрицы символов и атрибутов, которые должны быть выведены на экран. После испытаний выяснилось, что в этом месте ошибочные атрибуты последних символов уже сформированы, и, значит, надо искать проблему не в выводе на экран, а в коде, который формирует эту матрицу буфера вывода.

Если мы с вами посмотрим на то, что такое эмуляторы терминала, то увидим, что они работают следующим образом. На входе эти программы получают последовательность байт (в которых кодируются как символы для вывода, так и различные управляющие коды), внутри себя они интерпретируют её как последовательность команд вида “добавь символ X”, “установи курсор в (1;3)”, “поменяй текущий цвет на Green” и на выходе выдают ту самую матрицу символов и атрибутов, которая выводится на экран.

Чтобы быстро понять, какие команды генерирует тестовая программа на ncurses, я расставил брейкпоинты на большинство методов класса Screen. И после некоторых мучений обнаружил, что при ширине экрана > 80 ncurses генерирует не те команды, что приходят, когда ширина экрана меньше 80 ! Выяснилось, что ncurses внутри оптимизирует свой вывод, и в проблемном случае для строки возвращает последовательность управляющих кодов, которая делает только удаление одного символа! А в конец строки после этого уже ничего не дописывается, и там появляется пустота.

Исцеление

Встал вопрос – почему эта штука работает в других эмуляторах терминалов ? За ответом было решено лезть в исходники gnome-terminal (точнее, vte – библиотеки, которую gnome-terminal использует для эмуляции), но они оказались намного менее читаемы по сравнению с konsole. И я попробовал вместо гномовских посмотреть исходники xterm, благо они оказались более понятны. И нашёл код функции удаления символа, из которого стало понятно, что после удаления символов будут дописаны пробелы справа, но не с пустыми атрибутами, а с теми, которые установлены сейчас в консоли.

Собственно, после этого уже не составляло труда дописать пару строк, исправляющих проблему. Протестировал на тестовом приложении, запустил и console framework, проверил работоспособность нескольких других консольных программ (mc, vim), всё было ок, оставалось предложить патч для исправления бага. Для этого оказалось достаточным написать комментарий с детальным описанием сути проблемы и приложить патч. В течение следующего дня патч был проверен и закоммичен в master.

Успех.jpg:

На этом подвожу итог с разборками в Konsole, рад как слон, что не придётся писать обходные пути в console framework для рендеринга в konsole-based эмуляторах терминала. Интересно, сколько времени пройдёт до тех пор, пока фикс не будет втянут в пакеты популярных дистрибутивов ? Прямо реально интересно, готов даже не лениться и периодически проверять самые ходовые дистрибутивы (Fedora, CentOS, OpenSuse, Debian, Ubuntu, Arch, Gentoo). Только не знаю, как.