"Stub" в переводе с английского - "пень, обломок, огрызок". Это в литературном переводе. В нелитературный перевод углубляться не будем. Скажем только, что это то самое, что показывает компьютер человеку, попытавшемуся запустить из-под DOS приложение, написанное для Windows. Обычно он показывает сообщение: "This program cannot be run in DOS mode". А вы что подумали?
В связи с понятием "stub-программа" обычно возникают три вопроса:
Как она работает? Чтобы ответить на этот вопрос, следует заглянуть во внутренности формата PE-файла - стандартного исполняемого модуля Windows. Там мы обнаружим следующее:
PE-формат - не единственный формат исполняемых модулей, который понимает Windows. Но мы не станем здесь рассматривать другие форматы, так как применительно к теме статьи это не имеет смысла.
Глупая DOS загружает exe-файл, обнаруживает в его начале привычный для себя заголовок и исполняет stub-программу, ничего не зная о наличии в файле еще чего-то, кроме слов "а пошел ты туда-то и туда-то".
Умная Windows поступает более изощренно:
Как сделать собственную stub-программу? Да очень просто! Напишите любую, сколь угодно сложную или простую DOS-программу. Только постарайтесь обойтись без извращений вроде перевода процессора в защищенный режим или показа картинок в режимах VESA: помните, что stub-программа должна сработать с флоппи-диска на 8086-й машине с текстовым монитором. Модель памяти можно использовать любую, кроме tiny - она для stub-программы не годится.
И это, к сожалению, неизбежно, как бы ни печалилась по этому поводу душа настоящего ассемблерщика. Ведь модель tiny порождает com-формат исполняемого модуля, не имеющий, как известно, никакого заголовка. А раз нет заголовка - то даже умная Windows не сможет узнать размер stub-программы и, соответственно, вычислить начало PE-заголовка. Глупая DOS ненамного легче переварит эту ситуацию. Обнаружив, что файл является исполняемым (имеет расширение exe, как положено в Windows), она выяснит также, что его фактический формат - com (отсутствует сигнатура MZ), и попытается-таки его загрузить и исполнить. Однако stub-программа сработает только в том случае, если размер исполняемого файла не превысит 64К, что для приложений Windows (даже написанных на ассемблере) - довольно редкое исключение. Скорее всего, ваш пользователь вместо цветного пожелания перейти, в конце концов, на новую ОС, получит сухое монохромное сообщение командного процессора о чрезмерном размере исполняемого модуля.
Важно! При компоновке stub-программы в командной строке компоновщика укажите недокументированную и загадочно звучащую опцию /KNOWEAS. Это опция вносит некие изменения в заголовок exe-файла. Если опцию не указать, то компоновка Windows-приложения со stub-программой окажется некорректна, о чем вы получите соответствующее предупреждение от линкера.
Подозреваем, что не все компоновщики поддерживают эту опцию. Однако link.exe из состава MS Developer Studio, конечно же, поддерживает.
Написанную stub-программу используйте на этапе компоновки своего Windows-приложения. Для этого в командную строку компоновщика добавьте опцию /STUB:"filename.exe", где filename.exe, как вы можете догадаться - это имя вашей stub-программы, при необходимости с путем. Обнаружив эту опцию, компоновщик заменит стандартную stub-программу на вашу.
Здесь настоящего ассемблерщика подстерегает серьезное разочарование. Проблема в следующем. Обычно при компоновке exe-программ для DOS в исполняемом файле резервируется довольно большое место под таблицу перемещения. Например, в порядке вещей, если таблица перемещения вольготно располагается между смещениями 1ch (конец заголовка) и 200h (начало сегмента кода). Даже если ваша stub-программа не содержит ни одного фрагмента, подлежащего перемещению, все равно 486 байт чистых нулей будет бессмысленно вбацано в нежное девственное тело вашего Windows-приложения. Задачка, стоит ли избегать такого варварского разбазаривания дискового пространства и как это сделать, предлагается для самостоятельного решения. Кое-какие идеи читайте далее.
Минимальная stub-программа. Заглянув внутрь исполняемого модуля какого-нибудь Windows-приложения, вы обнаружите, что стандартная stub-программа чаще всего занимает 128 байт. Можно ли уменьшить этот размер и до какой величины? Отвечаем: можно, до 64 байт.
Возьмите любой редактор бинарных файлов и создайте с его помощью вот такой файл:
000000 4D 5A 00 00 01 00 00 00 02 00 00 00 FF FF 00 00 000010 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 000020 B4 4C CD 21 00 00 00 00 00 00 00 00 00 00 00 00 000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Присвойте ему имя, например, stub.exe и используйте в качестве stub-программы.
Вот расшифровка содержимого файла stub.exe с комментариями:
| смещение | значение | назначение | комментарий |
| +0 | 5A4D | Подпись exe-файла ('MZ') | |
| +2 | 0000 | Длина последней неполной страницы образа, байт | Игнорируется операционной системой |
| +4 | 0001 | Длина образа, страниц (страница = 512 байт) | Программа занимает менее одной страницы |
| +6 | 0000 | Число элементов в таблице перемещения | В этой программе перемещаемых элементов нет |
| +8 | 0002 | Размер exe-заголовка, параграфов (параграф = 16 байт) | Указывается размер базовой части заголовка. С учетом остальных значений параметров в данном случае означает, что исполняемый код в файле начинается со смещения 20h, и стартовый адрес находится в начале исполняемого кода |
| +0ah | 0000 | Минимум требуемой памяти за концом программы (параграфов) | В данном случае смысла не имеет |
| +0ch | FFFF | Максимум требуемой памяти за концом программы (параграфов) | Традиционно отводится вся доступная память |
| +0eh | 0000 | Сегментное смещение сегмента стека (для установки регистра ss) | |
| +10h | 0040 | Значение регистра sp (указателя стека) при запуске | В этой программе стек не имеет значения |
| +12h | 0000 | Контрольная сумма исполняемого модуля | Не используется |
| +14h | 0000 | Значение регистра ip (указателя команд) при запуске | Стартовая точка совпадает с началом кодового сегмента |
| +16h | 0000 | Cегментное смещение кодового сегмента (для установки регистра cs) | |
| +18h | 0040 | Cмещение в файле 1-го элемента перемещения | В этой программе ни одного элемента перемещения нет, а исполняемый код находится внутри заголовка, поэтому данное значение совпадает с концом программы |
| +1ah | 0000 | Номер оверлея | Здесь не используется |
| +1eh | 0000 0000 |
Резерв - 4 байта | |
| +20h | 4CB4 21CD |
Исполняемый код: mov ah,4ch int 21h |
В данном случае программа просто возвращает управление операционной системе. Максимальный размер исполняемого кода для данных значений заголовка - 28 байт (смещения 20h...3bh). Обратите внимание, что исполняемый код с целью экономии размера программы находится в области, отведенной под exe-заголовок! |
| +3ch | 0000 0000 |
Зарезервированное двойное слово | Используется компоновщиком Windows-приложения для размещения смещения PE-заголовка |
Вероятно, уменьшить размер stub-программы до величины, меньшей 64 байт, невозможно. А вот интересный вопрос: возможно ли вообще изъять stub-программу из модуля? Не спешите отвечать "нет"! 16-битные компоновщики от Microsoft имели в синтаксисе def-файла конструкцию вида STUB NONE, которая, согласно документации, предназначалась для использования при сборке dll-библиотек. В 32-битных компоновщиках эта возможность пропала. А жаль...
PS:
Вот уж в самом деле, "никогда не говори "никогда"! 24 июля 2001 года, в день падения 114-летнего рекорда жары в Москве, пришло письмо:
"На вашем сайте assembler.ru в статье "Минимальная stub-программа" автором упомянуто, что создать stub короче 40h байт, вероятнее всего, невозможно.
Привожу пример stub'а длиной в 20h байт:
00000000 4D 5A 00 00 01 00 00 00 01 00 00 00 FF FF 00 00 MZ.............. 00000010 B4 4C CD 21 00 00 00 00 40 00 00 00 00 00 00 00 .L.!....@.......В отличие от примера, приведенного в статье, размер заголовка уменьшен до одного параграфа. В терминах статьи это означает, что stub-код располагается по смещению 10h от начала файла.
Очевидно, после такого "сокращения" области заголовка от 10h и выше продолжают использоваться. Но в данном случае это неважно, так как на месте 4CB4h находится initial SP, а на месте 21CDh - checksum. Ни то, ни другое роли не играют. К сожалению, stub-код ограничен в 4 байта, потому что уже следующий word - initial IP.
Далее, в файле со stub'ом в 20h байт смещение 3Ch от начала файла приходится на смещение 1Ch PE-заголовка. Здесь расположен dword, который, по-видимому, ни на что не влияет, так что можно спокойно ставить туда 00000020h:
00000020 50 45 00 00 4C 01 07 00 21 B1 97 36 00 00 00 00 PE..L...!..6.... 00000030 00 00 00 00 E0 00 0E 01 0B 01 04 14 20 00 00 00 ............ ...Такую штуку вряд ли поддерживают компоновщики (мне от них этого добиться не удалось), я лично правил EXE вручную. Зато работает.
Напоследок - пример тоже рабочего stub'а длиной в 18h байт - меньше, по-моему, некуда:
00000000 4D 5A 00 00 01 00 00 00 00 00 00 00 B0 21 CD 29 MZ...........!.) 00000010 B4 4C CD 21 0C 00 00 00 50 45 00 00 4C 01 07 00 .L.!....PE..L... 00000020 21 B1 97 36 00 00 00 00 00 00 00 00 E0 00 0E 01 !..6............ 00000030 0B 01 04 14 20 00 00 00 00 2E 00 00 18 00 00 00 .... ...........Из-под DOS он выводит восклицательный знак (21h).
Ну как, размер достоин настоящего ассемблерщика?"
автор - Grief (soul_inspector@chat.ru)
Действительно, ни отнять, ни прибавить: только в голову настоящего ассемблерщика могут придти трюки, позволяющие так, чтобы помягче сказать, использовать формат исполняемого файла. Еще раз, вот они:
Конечно же, заставить какой-нибудь компоновщик создать исполняемый файл с предлагаемыми stub'ами не удастся. Такие трюки придется делать вручную, с помощью какого-нибудь бинарного редактора: переместить образ приложения внутри файла на нужное смещение, отредактировать exe-заголовок, PE-заголовок, заголовки секций - ну, в общем, кто знает формат - тот справится. (А лучше доверить эту рутину какой-нибудь маленькой программуле.)
Ну и напоследок, опровергнув свои же слова что "...меньше некуда...", Grief прислал вообще шедевр - вполне работающий stub длиной всего 0Ch!!! Разобраться, как он устроен, вы сможете самостоятельно, руководствуясь уже достаточно ясной идеей: в заголовках полным-полно никому не нужных полей, которые мы можем использовать для своих целей. Итак:
00000000 4D 5A 00 00 01 00 00 00 01 00 00 00 50 45 00 00 MZ..........PE.. 00000010 4C 01 07 00 08 00 00 00 B0 21 CD 29 B4 4C CD 21 L........!.).L.! 00000020 E0 00 0E 01 0B 01 04 14 20 00 00 00 00 2E 00 00 ........ ....... 00000030 18 00 00 00 00 E0 00 00 00 10 00 00 0C 00 00 00 ................
[C] Svet(R)off