Notepad почему не пишет

Обновлено: 01.07.2024


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

Тем не менее, сам факт довольно странный, поэтому мы попробуем найти эту ошибку в коде 64-битного и 32-битного notepad.exe от windows 7, исправим её, и выясним наконец, почему же она возникла. Заключается ошибка в следующем:

Если в блокноте включена опция «перенос по словам» (word wrap), то после сохранения файла начинаются всевозможные глюки: строки начинают разъезжаться, курсор улетает, текст вводится не туда, куда вы ожидаете, и так далее.

Для начала попытаемся поточнее выяснить, что же происходит. Откроем или введём какой-нибудь текст с длинными строками, чтобы они переносились. Сохраним файл. Если теперь попытаться его редактировать, например, добавив слово «синими», строки будут переноситься неправильно, ломая форматирование:


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


Если же теперь снова сохранить файл, станет ещё хуже. Все строки переформатируются, но окно не обновится. Поэтому курсор может переместиться в другое место, а если начать вводить текст, окажется, что вы вводите его не в то место, где находится курсор, а совсем в другое. Программисты, которые писали notepad, рассуждали логично: при сохранении файла ничего в окне не должно поменяться, поэтому и нет смысла его обновлять. Но в нашем случае с учётом этой ошибки весь текст меняется. Воспроизвести ситуацию может каждый пользователь windows, потому что последняя версия, где этой ошибки не было — Windows'98, и вряд ли у кого она ещё осталась.

Итак, по всей видимости, при сохранении файла что-то идёт не так и текст портится. Как найти это место в коде? Откроем notepad.exe в каком-нибудь отладчике. Как известно, в 64-битной системе для совместимости имеется два блокнота: 32- и 64-битный, надо не перепутать их.

Введём текст, на котором легко будет увидеть, как он портится при переносе строк. Наберём в одну строку «first text line second text line», а затем уменьшим окно так, чтобы она разрезалась посередине.


Резонно будет предположить, что запись делается с помощью функции WriteFile. Оказывается, она вызывается в коде целых 6 раз. Недолго думая, поставим точки останова на все 6 вызовов. Запускаем блокнот и нажимаем «сохранить». Выполнение останавливается здесь:


Посмотрим все регистры, где содержатся параметры вызова. В rcx у нас 104, это непонятно что. A rdx = 002D45E0, это похоже на адрес в памяти. Посмотрим, что там.


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


Ага, перед сохранением текст видимо преобразовывается из многобайтовой кодировки в однобайтовую. Точно так же, как в прошлый раз, посмотрим параметры. rax = 002D45E0, здесь у нас пока нули. Это как раз то место, куда попадёт результат. esi = 20, это длина текста. есх = 4еЗ, без комментариев. edx = 400, то же самое. А вот r8 = 002D6780:


Снова продолжим выполнение, наблюдая за содержимым этого участка памяти. Через несколько десятков команд мы выходим из подпрограммы, выполняются какие-то переходы, вызовы, но мы, не обращая на это внимания, продолжаем давить на «step over», выполняя код по шагам, и следя только за окном с текстом. И вот в какой-то момент он изменяется. Как видим, между 1 и 2 строкой появились коды 0d, 0d, 0a:


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


Можно попробовать, что будет, если не делать этот вызов. Снова доходим до этого места, и прямо тут, в отладке, изменяем RIP (регистр, где хранится адрес выполняемого в данный момент кода) на 00000000FFA38EE1, как будто мы пропустили этот call, который нам всё испортил. Удивительно, всё работает, текст не ломается!

Тут надо сказать, что в таких случаях обычно не разбираются, что это за подпрограмма, что она делает и зачем, а просто выкидывают её из EXE-файла. Это можно сделать разными способами, например, забить её всю NOP'ами, или изменить условный переход по равенству «je», который так кстати имеется сразу перед ней, на безусловный «jmp».


Параметр wParam: true — вставить символы, false — удалить их.


Где же здесь параметр, равный 1? Всё очень просто — он в регистре r8. Для сокращения кода компилятор никогда не использует прямую пересылку нуля в регистры. Такая команда занимает б байтов: 2 байта код операции, 4 байта — 32-битный ноль. Вместо этого регистр XOR-ится сам с собой, в итоге получается ноль, и это занимает всего 3 байта. После этого r9, который равен нулю, пересылается в r8 с добавлением единицы (выделена зеленым). Эта операция тоже занимает всего 4 байта. Вот эту зеленую 1 нам и надо поменять на 0, и тогда текст не будет портиться.

А теперь найдём эту же процедуру в 32-битной версии блокнота. Если не хочется повторять все те же манипуляции с отладкой, её можно найти простым поиском числа 0C8h.


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

Решение

Для того, чтобы в Notepad++ массового выполнить перенос на новую строку необходимо:


Справочная информация. Наборы выражений Notepad++

. «Точка» представляет один любой символ;
^ Начало строки;
$ Конец строки;
^$ пустая строка (начало и конец, между которыми пусто);
.+ любая не пустая строка;
\s Пробел;
\S Не Пробел
\w буква, цифра или подчёркивание _;
\d Любая цифра;
\D Любой символ, но не цифра;
8 Любая цифра;
[a-z] Любая буква от a до z (весь латинский набор символов) в нижнем регистре;
[A-Z] Любая буква от A до Z в ВЕРХНЕМ регистре;
[a-zA-Z] или [a-Z] Любая буква от a до z в любом регистре;
* «Повторитель». Означает, что предшествующий символ может повторяться (0 или более раз);
.* Абсолютно любой набор символов. Например, условие <p> .*</p> найдет все что между тегами <p> </p>;
(^.*$) Любой текст между началом и концом строки;
(44*.) ищет любые цифры, в данном случае двухзначные цифры;
\n Ищет символ новой строки;
\r Ищет пустые строки содержащий символы «перевод каретки» ;
^$ Ищет пустые строки
\n\r Ищет пустые строки содержащий символы — символ новой строки и «перевод каретки»
\s Ищет класс пробельных символов. К пробельным символам относятся пробел, символ табуляции, возврат каретки, символ новой строки и символ перевода страницы. То же самое, что и [ \t,\r,\n,\f];
\S Ищет класс не пробельных символов. То же самое, что и [^ \t, \r,\n,\f];
^\s*$ Ищет пустые строки содержащие пробел;
^[ ]*$ Ищет пустые строки содержащие пробел;
^Слово Ищет слово «Слово» в начале строки;
Слово$ Ищет слово «Слово» в конце строки;
\bдол Ищет набор символов «том», только в начале слов, то есть в слове Долина будет найдено, а в слове Подол нет;
дол\b Ищет набор символов «дол», только в конце слов, то есть в слове Долина не будет найдено, а в слове Подол да;
\Bдол\B Ищет набор символов «том», не в начале и не в конце слов, то есть в слове Долина не будет найдено, и в слове Подол нет, а вот в слове Подольск будет найдено;
| — Регулярное выражение, «или». Будет искать то что слева и справа.

Примеры:

Найти (^.*$) Заменить \n\r — находит новую строку и добавляет к ней пустую строку;
Найти (^.*$) Заменить <p></p> — находит новую строку и заключает её в теги <p></p>;
Найти \n\r Заменить «оставляем пустым» — Удаляет пустые строки
круглые скобки обязательны, иначе найденное будет изменено на заменяемое.

Читайте также: