Категория: Компьютеры

Заметки о программировании и на околокомпьютерные темы

Вложенные категории: Delphi, Игры

Об анинсталлерах

Одно из самых жутких, заброшенных и тоскливых мест Windows – это управление приложениями. Целиком мне сейчас о нём говорить не хочется, скажу только об одном аспекте: об анинсталле. Как он устроен – это дичайший бред. Попробуйте удалить несколько случайных продуктов на системе, которая проработала пару лет!

Не найден msi-файл установки, удаление невозможно.
Пропал компонент приложения, удаление невозможно.
Кто-то перезаписал мою регистрацию, удаление невозможно.
Кто-то вручную удалил с диска файлы, удаление невозможно.

(далее)

Это бред сумасшедшего. Как это удаление может быть невозможно?! Мне нужно не установить, а удалить! Слышите – удалить! Программу. Просто взять и удалить. Неужели это так сложно? Неужели для удаления файлов действительно требуется, чтобы они все до единого были на месте, чтобы сохранился пакет установки программы?
Да нет конечно же! Это лентяи-программисты заставляют пользователей плясать под свою дудку. Вместо того, чтобы подумать головой и написать нормальный деинсталлятор, они предпочитают переваливать все проблемы на плечи пользователя. Нет уверенности, что мы удалим всё? Ну так не будем удалять ничего! Пусть пользователь сам разбирается.

Удаление, уничтожение, очистка – это операции, которые должны выполняться “наилучшим возможным образом”. Т.е. в каждой ситуации должен удаляться абсолютный максимум того, что можно удалить. Если каких-то файлов уже нет – удаляем те, что остались. Если установочного пакета нет – ладно, удаляем хотя бы то, о чём сохранились записи. Не сохранилось записей – удаляем хотя бы то, о чём можно догадаться без них.

Ошибок в этих операциях не должно существовать в принципе. Они должны выполняться всегда.

В качестве бонуса: этот же принцип относится и к деструкторам объектов в программировании. Деструктор должен работать всегда. В каком бы состоянии не находился объект, деструкция, деинициализация, и вообще всё, что угодно, с приставкой “де-“, должно игнорировать ошибки и удалять всё возможное.

Это не значит, что надо ловить в деструкторе все ошибки и затыкать им рты. Если ошибка уже случилась, её, как обычно, нужно вывести пользователю. Просто деструктор должен быть написан так, чтобы выполнять максимум без ошибок даже в неизвестных обстоятельствах. Например, даже если деструктор у вас вызывается единожды, и lpMyObject всегда существует, нужно писать:
if (lpMyObject != NULL) {
FreeMyObject(lpMyObject);
lpMyObject = NULL;
}

Хорошая практика – это чтобы ваш деструктор можно было вызвать на любом этапе жизни объекта, и он успешно выполнился бы. Даже в середине конструктора. Даже в процессе выполнения какой-то критической функции. Потому, что деструктор не может позволить себе вызывать исключения: его самого, вероятно, вам придётся вызывать, если вы надеетесь программно обработать какие-то исключения. Следовательно, деструктор уже должен быть готов обрабатывать исключительные ситуации.

Хостинг

Опытным путём установлено, что хостинг бывает двух видов:
1. Именитые надёжные хостеры, которые брать меньше 100 рублей в месяц считают ниже своего достоинства. Даже за ерунду. Даже за “просто пальчиком потрогать”. Деньги! На бочку! Неважно, что бесплатные неограниченные почтовые ящики можно заказать у гугла. У нас они стоят денег! Много денег! Ведь гугл не хостит сайты, а мы хостим. Монополия… Ах, как приятно звучит это слово… Ах, как… Простите, о чём это мы. В кассу, пожалуйста.
2. “Начальство сказало предоставлять хостинг, значит, предоставим хостинг. Все покупайте наш Хостинг! Наш Хостинг самый Лучший Хостинг! Клипарт с улыбчивым дядей не даст соврать. Наш сервер стоит у нас в серверной и круглосуточно поддерживается! На нём установлен LINUX SERVER. Регистрация under construction, но вы пишите на klienty@ultra-line-resheniya.ru, мы договоримся.”

Об альтернативных способах ввода

Билл Гейтс любит гадать на кофейной гуще, предсказывая будущее. В последний раз он, кажется, заявил, что будущее за тачскринами. А вот как на самом деле:

Тачскрин – никогда и нигде не будет популярным средством ввода. Наши руки привыкли работать с объёмными предметами, которые можно ощупать, дёрнуть, нажать. Вазюкать ими по холодному стеклу – просто предел мечтаний футуриста-идиота.
Голос – тоже не будет. Печатать быстрее, чем диктовать. В общественном месте, да и дома, разговаривать неудобно. И вообще, клавишами управлять быстрее. Голос, однако, незаменим в играх – об этом я раньше писал, но пост искать лень.
Рукописный ввод – только для математических формул и нот.

Попытки перенести в компьютер “привычные” средства ввода (голос и рукопись) сродни попыткам выдалбливать на бумаге наскальную живопись.

Связи

Удобство использования библиотеки зависит от связей. Мы это видели дважды: один раз сам интернет оказался удобен из-за гиперссылок, другой – Википедия покорила всех именно способностью привязывать к каждой вещи всё-всё-всё, что хоть как-то с ней связано.

В MSDN сейчас больше всего нехватает связей. “Эта статья на других языках, для других версий Windows, в других выпусках Visual Studio”. По двадцать похожих статей – и никаких связей.

О USB 3.0

Скорость USB 3.0 – в десять раз выше старой. Но максимальная длина кабеля – три метра, и он толстый. Кроме того, коннекторы из простых “воткнул – работает” превратились в сложные конструкции с несколькими слоями контактов.

Сдаётся мне, интерфейс USB повернул куда-то не туда. Лучшим в нём всегда была простота.

Вот кто, интересно, выдумал класть программы в “Program Files\Company Name\Application Name“? Хотел бы я посмотреть на этого идиота.

Мало того, что для нахождения программы нужно вспоминать ещё и имя её разработчика (а кто его помнит?). Мало того, что компании меняются, а программы остаются (флеш, например, был вначале Macromedia, потом Adobe; Delphi – вначале Borland, потом Inprise, опять Borland, CodeGear и Embarcadero; почти любая крупная игровая серия сменила пару разработчиков).

Самое главное, что в такой группировке нет никакого смысла. Это пример ложной общности. Программы роднит вовсе не принадлежность к одному издателю, а схожесть функций. Если у меня есть пять игр Myst, я хочу их видеть в папке “Games\Myst” или “Games\Quest\Myst”, а не “Cyan”, “Presto”, “Ubisoft” или “Broderbund”. Особой весёлостью отличается пятый Myst, который кладёт себя в папку “Ubisoft\Cyan\Myst V”. Спасибо, очень приятно чувствовать себя декодирующей машиной.

Кто-то может сказать, что в папку “Program Files” обычному юзеру лазать не полагается. Охотно верю. Откройте “Пуск”! Откройте, откройте. Там та же самая фигня. “Пуск -> Программы -> Имя паблишера -> Имя разработчика -> Название игры”. И это при том, что от одного разработчика редко найдётся хотя бы тройка игр! Чего уж говорить о паре “разработчик – паблишер”.
Психбольница во всём цвету.

Как дополнительный бонус: группировка по компаниям не избавила нас даже от длинных названий программ.
“Microsoft Office -> Office Applications -> Microsoft Word”.
“Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 Remote Debugger”
В переводе с менеджерского на русский это значит:
“Microsoft Office -> Word” и
“Microsoft Visual Studio 2005 -> Tools -> Remote Debugger”

Да, кстати

Меня раздражает любовь виндов класть папки от апдейтов прямо в \WINDOWS. Так здорово наблюдать в ней двадцать штук папок вида $NtUninstallKB123456$!
Ну неужели нельзя было завести для этого отдельную папку?

Ещё о том, что требует срочного исправления:

1. Должна быть общая, стандартная, единственная система установки приложений. В ней всё должно быть предельно точно описано: в каком ключе реестра хранятся какие сведения об установке, в каких папках – какие временные файлы.
Все старые инсталляторы должны работать исключительно через слой совместимости, который подбирал бы раскиданное ими по различным папкам барахло и складывал его в папочку “Installed Applications\AppName\Compability”. Задолбало смотреть, как пятьдесят различных инсталляторов складируют барахло в пятидесяти различных местах.

2. Кто-то должен разобрать все подпапки винды, и выкинуть половину. Потому, что обычная практика заводить по папке на каждое приложение. Или даже по нескольким.

Чего нехватает инсталляторам

…так это странички со вступлением.
“Вы устанавливаете Stochastic Lab Archim, программу для построения двух- и трёхмерных графиков математических функций”.

А то комедия сплошная, лицензионное соглашение всегда покажут, а что устанавливается – одному чёрту известно. Считается, видимо, что файл установки используется лишь однажды, сразу же после скачки, когда пользователь ещё помнит, что он такое скачивал.

Anidb-tool

Вместо того, чтобы готовиться к комплексному анализу, написал простенькую утилиту для добавления файлов в mylist на AniDB. Работает так: указываете ей свои логин/пароль, и дальше можете запускать из командной строки:
anidb mylistadd “имя файла”

Ещё в комплекте есть два batch-файла, с помощью которых можно делать так. Кликаете по файлу или папке с видео правой клавишей мыши, выбираете “Отправить> anidb_file/anidb_folder”, и файл/все файлы в папке добавляются в ваш mylist на AniDB.

Всё это нужно потому, что меня мега-задолбала огромная, глючная и неудобная AniDB-o-Matic. А нужно мне из неё было только авто-хэширование файлов и добавление в mylist. Вот я и сделал его отдельно в маленькой утилитке.

Компьютерная история

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

А теперь постановка задачи

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

Примерно каждый восьмой раз записанная от корки до корки флешка не запускается. Винда вылетает в чёрный экран при загрузке с надписью
File is missing or corrupt – C:\WINDOWS\SYSTEM32\CONFIG\SYSTEM
Это, к сведению, файл с ветвью HKEY_LOCAL_MACHINE системного реестра. Ошибка повторяется при перезагрузках. Указанный файл на диске есть, более того – он читается, и его можно подгрузить в реестр на любой рабочей машине (“File -> Load hive” в regedit, если кто не знает). Теперь сюрприз: чтобы исправить ошибку, достаточно перенести этот файл на другой диск, а затем перенести его обратно. После этого винда с флешки загрузится.

Почему операционная система на другом компьютере читает файл целиком и нормально, а та же операционная система при загрузке не может его прочесть?
Может быть, файл читается не с первой попытки, а на этапе загрузки глупая система не догадывается попробовать снова? Заряжаю procmon – это утилита, перехватывающая обращения к диску и реестру. Нет: в работающей операционной системе все запросы на чтение выполняются без ошибок. Но это мало чего даёт, драйвер может сам бороться с ошибками, не сообщая об этом наружу.

Пишу простое приложение, читающее диск по секторам. Замеряю время отклика: одинаковое для всех секторов. Вряд ли где-то есть повторное чтение. Попутно выясняется ещё одна любопытная вещь: если со сломанного диска снять образ, а потом записать его обратно, то он начнёт грузиться!

Ну что тут поделаешь? Под работающей системой всё безупречно. При загрузке стабильно ошибка. Логики никакой. Будь это пользовательское приложение, я бы его дизассемблировал и отладил, но тут ошибка в режиме ядра. При загрузке системы. Там ещё и графики нет.
Сдаваться?
Чёрта с два.

Приобретаю нуль-модемный кабель – это COM-COM, замкнутый крест-накрест, что-то вроде кроссовера для Ethernet. Соединяю тестовый компьютер с рабочим, на рабочем запускаю WinDbg, а на тестовом – вы уже догадались, к чему дело идёт? – пишу в boot.ini “/DEBUG /PORT=COM1 /BAUDRATE=115200”.
После часа мытарств оно подключается, соединяется, срабатывает брейкпоинт и я уже замахиваюсь руками над клавиатурой – сейчас я буду отлаживать ядро операционки! – , но… фиг вам. Не судьба. Ошибка происходит до инициализации дебаггера. Ошибка происходит вообще до загрузки NTOSKRNL. Ошибка в NTLDR.

Краткий экскурс в сторону. Как загружаются все Windows NT, за исключением Висты и Server 2008.
Вначале работает BIOS. Сделав свои дела, он передаёт управление коду в загрузочном секторе активного раздела диска. Этот код очень маленький, и делает он только одно: находит на диске файл NTLDR, и передаёт управление ему. NTLDR – это загрузчик системы. В него встроены драйвера FAT и NTFS, при помощи которых он читает с диска boot.ini, и предлагает пользователю выбрать систему. Если у вас несколько операционок, вы наверняка видели это меню. Если операционка одна, меню на экране не появляется, но вы всё равно можете в него попасть, долбя по клавише F8 при загрузке.
Если выбранная система – DOS или 9x, NTLDR просто передаёт управление сохранённому в файле загрузочному сектору DOS. Иначе он сам начинает загружать NT: подгружает HAL и пару драйверов, затем читает реестр в память, находит в нём список boot-time драйверов (это первичные драйверы, самые необходимые для загрузки – драйверы жёстких дисков и файловых систем, чипсета, и тому подобного). К слову, в это время драйверы NTFS ещё не загружены! Вообще ничего ещё не загружено! В NTLDR намертво вкомплирован свой собственный драйвер NTFS, которым он и пользуется на первых порах, пока не загрузит boot-time драйверы.
И наконец, когда вся эта пляска закончена, управление передаётся NTOSKRNL.EXE, который и загружает далее операционную систему. Где-то в нём запускается отладчик, позволяющий контролировать загрузку через COM-port.

Лирическое отступление было нужно для того, чтобы вы полностью осознали: в момент, когда грузится реестр, нет ничего. Вообще НИЧЕГО. Это даже не режим ядра, это вообще чистый ассемблерный код, пользующийся только собственными сервисами и биосом.
Сдаёмся?
Ну конечно же, нет.

Поисками в торрентах и по интернетам была найдена отладочная версия ntldr. Эта дикая редкость поставлялась вместе с DDK, причём c XP_DDK, который больше не распространяется, поскольку его заменил 2003_DDK, содержащий всё то же самое, кроме одного. Этой самой отладочной версии ntldr. Можете себе представить, что она нужна максимум паре тысяч человек на планете, и у большинства из них она давно есть.
Был скачан пакет символов для checked-версии XP Rtm. Ни один пакет символов на сайте не подходит к ntldr из XP_DDK, и в самом DDK символов для неё не было (о чём они вообще думали?!), но checked-версия чистой XP подходит более-менее (начала функций прыгают на пару десятков инструкций в стороны, но это уже мелочи).

Дебаггер запустился, подключился и успешно застыл ещё до загрузки HAL. Я ликовал. Но ликование моё было преждевременным.
Убив почти сутки на тупой разбор ассемблерного кода, исписав кипу бумаги адресами и изрисовав блок-схемами, я вычленил виноватых. Во-первых, файл реестра открывался. Во-вторых, он читался до определённой точки. В третьих, очередной кусок файла не читался, причём ошибку возвращала даже не функция Ntfs, а вызываемая ей функция низкоуровневого чтения с диска. То есть, тупо не читается какой-то сектор. Но почему?
Я влез в функцию чтения. Функция чтения была короткой.

push 058h
push 021Ch
retf

Неплохо, да? Учитывая, что ассемблер я учил на ходу, я развеселился. (Вообще, я веселился всё время, пока разбирал код загрузчика. Иногда я веселился так, что мне хотелось биться головой об стол от смеха). Какого чёрта?
А всё просто. До сих пор адресное пространство было flat – и режим уже был защищённым, это постарался загрузчик ещё до того, как запустил дебаггер. А этой функции, чтобы сделать запрос к int 13h, потребовался segemnted-режим. Она запихала в стек оффсет и адрес, и сделала retf, который вытащил оффсет с адресом и перешёл по ним. В следующее мгновение я уже был в сегментной адресации. И там меня ждал сюрприз, плохой. Дебаггер не работал. Единственное, что он мог – это исполнить первую команду. На второй или любой из последующих отлаживаемый компьютер перезагружался.

А мне нужно было отлаживать именно эту функцию. Во всяком случае, я хотел узнать, что передают в int13h, и что она возвращает. Что делать? Ну ясно, что. Я сделал дамп памяти функций, исполняющихся в сегментированном режиме, и стал их разбирать.

Оказалось, в этой несчастной функции вызова int13 всё устроено примерно так:
1. Создать новый стек по фиксированному адресу.
2. Скопировать старый стек в новый.
3. Вызвать функцию, делающуют НЕИЗВЕСТНО_ЧТО в одну сторону (НЕИЗВЕСТНО_ЧТО включало в себя перезапись таблицы прерываний и глобальной таблицы дескрипторов, я подозреваю, какие-то трюки с режимом исполнения. Скорее всего, управляемый real режим под protected).
4. С кучей мытарств подгрузить из стека параметры и вызвать int13.
5. Вызывать функцию, делающую НЕИЗВЕСТНО_ЧТО в обратную сторону.
6. Выгрузить новый стек, восстановить старый.
7. Ну и запихать в стек flat-адрес и сделать нормальный ret.

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

Я разобрался, какие параметры передаются, и что приходит в ответ. На нормальный набор параметров (и да, я знаю, что блоки памяти должны не пересекать границу сегмента) биос возвращает 8012h. 80 в AH (то есть, это код возврата – “Drive not ready, timeout”), 12 в AL (по документации это число прочитанных секторов, но экспериментально я убедился, что он возвращает 8012 и тогда, когда просто выдёргиваешь флешку ещё до выполнения int13).

Вот теперь я почти в тупике. Ну да, я дизассемблировал всё на свете до самого источника проблемы. Ну да, я знаю, что на нормальный запрос биос возвращает ошибку. Я только не знаю, почему.
Есть какие-нибудь идеи, что делать дальше? Я так подумываю дампнуть биос, лол. Где наша не пропадала! Ещё есть идея загрузиться из под доса, и повторить int13, приводящий к ошибке, вручную – но только что мне это даст, каким бы не был результат?

Ну да, ещё можно вставить флешку в другой компьютер. Тоже мысль. Надо было сразу попробовать. Но хотелось бы не просто найти виноватых (уже ясно, кто они: пара флешка-биос), а ещё и придумать что-нибудь, что спасло бы сотни стоящих в терминалах флешек от апгрейда. Ну например, если я пойму причину ошибки, я смогу переписать прошивальщик так, чтобы он шил только в “безопасные” сектора – те, что биос читает.