|
ArcaOS 5.0 Русская версия
Пакет русификации ArcaOS 5.0 OS/2 давно доступен.
Поддерживается любая версия: 5.0, 5.0.1, 5.0.2.
eCo Software может выпустить и другие пакеты
(Немецкий, Голландский, Бразильский Португальский, Испанский, Шведский и т.д.)
|
Конкурс миниатюризации для OS/2 |
TITLE: Конкурс миниатюризации для OS/2
DATE: 2001-10-02 16:44:08
AUTHOR: Андрей А. Породько
Перевод с английского: Андрей А. Породько.
Оригинал:
Секреты мастеров программирования раскрыты!
(Michal Necasek, Сентябрь 2001)
В августе 2001 меня посетила дикая идея: я собрался и анонсировал в
группе новостей comp.os.os2.programmer.misc "Конкурс миниатюризации для
OS/2", конкурс для программистов OS/2, целью которого должно было быть
создание минимально возможной программы удовлетворяющей следующим
условиям. Наиболее важными из них были:
- Программа должна быть в формате OS/2 LX исполняемого модуля (32-бит);
- Программа должна выводить сообщение "I'm really small!" (Я действительно мала!) и перевод строки на консоль;
- Программа должна запускаться в OS/2 Warp 4 GA или более новой версии без добавления любих файлов сверх стандартного набора.
Для того, чтобы сделать конкурс более интересным и привлекательным для
программистов, имеющих различный опыт, сначала я объявил конкурс в двух
категориях, которые потом были дополнены еще одной:
- Stock - разрешено использование любого языка высокого уровня и широкораспространенных инструментов. Использование ассемблера запрещено.
- High Octane Stock - тоже самое что и выше, но с применением ассемблера.
- Custom - (я бы сказал free style, прим.пер.) разрешено все, только бы программа работала и инструмент с помощью которого она была изготовлена был бы доступен.
Для минимизации организационных усилий и чтобы соревнование было
честным, я использовал одну удачную идею: все конкурсанты сообщали
мне только минимальные достигнутые ими размеры по каждой предложенной
категории, и только финалист должен был опубликовать свой исходный код и
передать его мне. Я регулярно публиковал достигнутые участниками
результаты и каждый знал, к чему стремится. Я думаю что именно это
привело к таким результатам.
Перед тем как объявить о конкурсе у меня были несколько своих идей как
получить очень маленький исполняемый модуль. Но даже я был удивлен
программой-победителем. Победителями в трех категориях стали:
- Stock - Knut St. Osmundsen, 276 байт, используя Watcom C, WLINK и LxLite.
- High Octane Stock - Knut St. Osmundsen, 273 байт, используя ALP, ILINK и LxLite.
- Custom - Martin Lafaix, 196 байт, используя в основом 16-тиричный редактор.
Возможно, наиболее интересным результатом конкурса явился тот факт что
программы на C и ассемблере очень незначительно отличаются размером, в
отличие от первоначальных ожиданий. Это в основном благодаря тому факту,
что Watcom C предоставляет очень хорошие средства управления
генерируемыми объектными файлами, почти такое же хорошее как в
ассемблере.
Но, конечно же, наиболее замечательный результат получил победитель в
категории Custom. Особенно если Вы понимаете что размер заголовка
LX-модуля составляет 196 байт и загрузчик программ OS/2 откажется
запустить что-либо меньшее по размеру 196 байт. Но тем не менее этот
исполняемый модуль размером с заголовок печатает 17-ти байтное сообщение
и корректно возвращает управление операционной системе. Я не буду здесь
обсуждать эту категорю в деталях и отправлю читателя к автору Martin
Lafaix'-у за разбором его программы-победителя (справедливо названному
Fandango on Core).
Теперь, после того как я обрисовал условия и ход конкурса, давайте
перейдем к рассмотрению исходных текстов. Исходные тексты, которые я
представлю, это мои собственные тексты, но с использованием многих идей
победителей. Я строил все программы с помощью Watcom 11.0c и их вполне
возможно приспособить к другим ассемблерам/компиляторам, возможно за
исключением наиболее продвинутого C-кода. Я буду объяснять все ключи
компилятора и линкера которые не совсем очевидны.
Я предполагаю что читатель имеет представление об использовании
ассемблера, C, архитектуры x86, формата исполняемого модуля OS/2 и среды
исполнения. Это определенно не для новичков даже если новичок сможет
перенять прием или два не перегрузившись. Если все же перегрузился, не
беда, возвращайтесь через год или два! Я тоже когда-то начинал и был
новичком.
Категория High Octane Stock
Сначала я хочу представить категорию, в которой разрешалось использование
ассемблера, так как текст здесь более "прямой" и не используется столько
таинственных приемов (трюков) как в C версии. Надо сказать, что в
ассемблерной версии все ясно видно, тогда как в C версии многие приемы
скрыты за используемыми прагмами и ключами компилятора.
Хорошо, как вы выводите сообщение на консоль с использованием OS/2 АПИ?
Первый очевидный выбор - через DosWrite. Но DosWrite имеет несколько
серьезных недостатков. Он требует четырех параметров (т.е. четырех
DWORD слов в стеке) и располагается в системной библиотеке DOSCALLS.DLL
(на самом деле в ядре OS/2), которая имеет достаточно длинное имя,
которое должно быть включено в исполняемый файл.
Давайте разберем внимательнее. В этом случае можно воспользоваться
другим, редко используемым, вызовом OS/2 АПИ, это DosPutMessage. Он
имеет только три параметра и, что еще лучше, располагается в MSG.DLL
(более короткое имя библиотеки).
Полностью исходный текст программы (названной asm1.asm) с использованием
DosPutMessage выглядит примерно так:
.386p
EXTRN DosPutMessage:BYTE
_DATA SEGMENT BYTE PUBLIC USE32 'STACK'
_msg:
DB "I'm really small!",0aH
_DATA ENDS
_TEXT SEGMENT BYTE PUBLIC USE32 'CODE'
ASSUME CS:_TEXT, DS:_TEXT, SS:_TEXT
startup:
push offset flat:_msg
push 12H
push 1
call near ptr flat:DosPutMessage
add esp,0CH
ret
_TEXT ENDS
END startup
Как Вы видите, эта программа очень короткая и не использует никаких
трюков. Единственным "трюком", пожалуй, является использование вместо
DosExit простого RETs. Это приводит к тому же эффекту, что и вызов
DosExit с параметрами по-умолчанию. После компилирования WASM-ом и
линковки WLINK-ом в конце концов Вы получите программу размером в 545
байт. Вот точный набор команд, который я использовал:
wasm asm1.asm
wlink file asm1 lib os2386 option st=32k
Единственным, не совсем очевидным ключом является st=32k который
устанавливает размер стека программы в 32 килобайта. Без этого ключа,
программа имеет только 18 байт стека (сообщение которое она должна
вывести) и трапнется сразу после старта.
С точки зрения "жирности" программы 545 байт не так уж и плохо, однако
легко уменьшить размер с помощью LxLite:
lxlite /T /ZS:512 asm1.exe
Здесь трюком является удаление стаба MS-DOS (ключи /T и /ZS LxLite),
размером в 128 байт, что значительно влияет на размер программы (в нашем
случае. прим. пер.). OS/2 вполне нормально запустит и программу без
этого стаба. MS-DOS не будет способен запустить это программу, но этого
и не требовалось по условиям конкурса. LxLite проделывает некоторую
дополнительную оптимизацию и результат - программа в 325 байт. Это
значительно лучше чем 545, но все еще далеко от победителей. Мастера
очевидно имеют какие-то козыри в рукавах. Хорошо, давайте посмотрим на
них.
Для достижения минимального размера необходимо атаковать с нескольких
направлений. Скорее всего одно из них позволит уменьшить размер
программы. Одним, таким не очевидным ходом является уменьшение
избыточности структуры LX модуля.
Но мы начнем с чего-то совсем отличного. Существует более чем один путь
уменьшения размера файла программы. Пока я думал что нет лучше способа
печатать сообщение чем использование OS/2 АПИ, система Warp 4 (базовая ОС с
конкурсе) предоставляет несколько менее известный путь, который подходит
гораздо больше. Warp 4 поставляется с динамической библиотекой среды C,
представляющей собой несколько видоизмененный вариант библиотеки от
VisualAge C++ 3-й версии. На самом деле это несколько DLL-библиотек -
LIBCS.DLL (для задач не использующих треды), LIBCM.DLL (для задач
использующих треды) и LIBCN.DLL (подсистема). В них имеются vprintf() и
puts() которые требуют только одного аргумента. Я выбрал puts() так как
это позволяет еще оптимизировать код, опустив символ перевода строки в
сообщении (puts() добавляет перевод строки автоматически). Тем не менее
ординал (ссылка в DLL на функцию. прим. пер.) для vprintf() требует на
один байт меньше в таблице импорта исполняемого модуля и, таким образом
результат будет таким же. Как бы то ни было, вот asm2.asm:
.386p
EXTRN puts:BYTE
_DATA SEGMENT BYTE PUBLIC USE32 'STACK'
_msg:
DB "I'm really small!"
_DATA ENDS
_TEXT SEGMENT BYTE PUBLIC USE32 'CODE'
ASSUME CS:_TEXT, DS:_TEXT, SS:_TEXT
startup:
mov eax,offset flat:_msg
jmp near puts
_TEXT ENDS
END startup
Заметьте что puts() (и vprintf() тоже) требует в качестве параметра
строку заканчивающуюся NULL, в отличие от DosPutMessage. Но так как мы
знаем (ведь правда?) что сегмент данных гарантированно инициализируется
нулями, мы этот факт опустим. Другой важный факт который следует
запомнить - это то, что puts() использует соглашение _Optlink для
вызова функций, при котором параметры передаются через регистры, а не
через стек.
Эта программа использует другой интересный прием оптимизации. Вместо
прямого вызова puts(), она делает на него переход. Когда puts() сделает
возврат, управление вернется непосредственно в тот модуль который
вызывал нашу программу, что избавляет нас от использования команды RET у
себя. И что еще лучше, такой прием оставляет переход на puts()
инициализированным нулями в файле исполняемого модуля, что позволяет
линкеру и/или LxLite еще сэкономить 4 байта. Наша программа теперь имеет
размер в 318 байт. Получите удовольствие от красоты программы, состоящей
всего из двух инструкций и которая, тем не менее, что-то делает, безо
всяких критических ошибок.
Теперь давайте пройдемся по действительно интересному.
Чтобы достичь размеров программ-победителей, нам еобходимо избавится от
45 байт лишнего веса. Это можеть быть невозможно, потому что программа и
так достаточно мала. Давайте попроказичаем и будем делать вещи, которые
детям делать не разрешают. Это на самом деле просто, но совершенно не
очевидно, это показано в листинге asm3.asm:
.386p
EXTRN puts:BYTE
_TEXT SEGMENT BYTE PUBLIC USE32 'STACK'
ASSUME CS:_TEXT, DS:_TEXT, SS:_TEXT
_msg:
DB "I'm really small!",0
startup:
mov eax,offset flat:_msg
jmp near puts
_TEXT ENDS
END startup
Да, у программы только один сегмент ! Этот сегмент не имеет флага
исполняемого, но она запускается и работает и что важно... что более
важно, программа имеет размер только 283 байт. Мы достигли размеров
программ-победителей! А можно ли еще сэкономть ? Тут два пути. Один
чрезвычайно прост: переименовать исполняемый модуль! Имя исполняемого
файла сохраняется внутри его самого и для экономии драгоценных байтов,
вы можете использовать пустое имя (.exe). Другой путь сэкономить место -
это первая инструкция программы. Команда MOV занимает пять байт в
исполняемом модуле тогда как JMP занимает только один байт (потому что
адрес заполнен нулямя и будет подставлен на этапе загрузки программы).
Так получилось, что мы знаем что команда MOV помещает в EAX. Это адрес
10000H (64K) потому что в линейной модели памяти исполняемый модуль
всегда загружается по этому адресу. Knut St. Osmundsen нашел более
элегантный метод загрузки значения 10000H в EAX длиной в 3 байта кода.
Вот финальная версия исходного текста программы (asm4.asm):
.386p
EXTRN puts:BYTE
_TEXT SEGMENT BYTE PUBLIC USE32 'STACK'
ASSUME CS:_TEXT, DS:_TEXT, SS:_TEXT
_msg:
DB "I'm really small!",0
startup:
dec ax
inc eax
jmp near puts
_TEXT ENDS
END startup
А вот команды, которые я использовал для получения исполняемого модуля:
wasm asm4.asm
wlink f asm4 n .exe imp puts LIBCS.362 op st=32k
ren .exe asm4.exe
lxlite /T /ZS:512 asm4.exe
Необычная последовательность команд WLINK, imp puts LIBCS.362 показывает
линкеру что символ (имя) должен быть экспортирован из библиотеки LIBCS
как ординал 362. Мы могли бы использовать и LIBCM, но так как наша
мини-программа имеет один тред, то и с LIBCS она работает нормально. Что
касается номера ординала, то его легко найти с помощью EXEHDR. Конечно
возможно испоьзование библиотеки импорта, но для получения доступа к
одной функции совсем не обязательно ее иметь. Это способ для тех бедных
(заблудших ? прим. пер. ;-) душ которые до сих пор не обзавелись Warp 4
Toolk для написания своих программ.
Хитрым приемем является также директива n .exe (Имя). Она указывает
WLINK использовать пустое имя для файла исполняемого модуля. И конечный
размер теперь 274 байта! Так как я использовал инструменты Watcom (по
крайней мере я так думаю), программа-победитель от Knut-а на один байт
меньше вследствие разницы между испольняемыми модулями получаемыми с
помощью WLINK и ILINK, и не существует способа проверить и
проконтролировать это. Хей, однако и 274 байт совсем не плохо!
Интересно также отметить что весь исходный код программы очень мал и
даже команды для получения исполняемого модуля не очень сложные. Но это
обманчивое представление, потому что программа явно использует некоторые
особенности архитектуры x86 и среды исполнения OS/2.
Категория Stock
В предыдущем разделе я раскрыл все важные приемы достижения
микроскопических размеров программы. Теперь мы попытаемся получить
сходные результаты без использования ассемблера. Это требует достаточно
хорошего знания специфики компилятора, такой как различные малоизвестные
ключи и прагмы. Я сразу перейду к финальному коду и проанализирую его.
Встречайте ! mini.c:
void puts(char *s);
#pragma data_seg("MYDATA", "STACK")
#pragma code_seg("MYDATA", "STACK")
char msg[] = "I'm really small!";
void _System startup(void) {
puts(msg);
}
Исполняемый модуль получается следующими командами:
wcc386 -s -g=DGROUP mini.c
wlink sys os2v2 name .exe f mini imp puts_ LIBCM.362 op start=startup,st=32k,nod
ren .exe minic.exe
lxLite.exe /T /ZS:512 minic.exe
Конечный исполняемый модуль, minic.exe, имеет размер всего 276 байт! Как
это возможно? Близкое изучение раскрывает только одно отличие между C и
ассемблерным вариантами машинного кода. C генерирует код эквивалентный:
mov eax,offset flat:_msg
jmp near puts
Да, компилятор чертовски умен и оптимизировал код настолько, насколько
это возможно! Из-за того что компилятор не имеет информации как
результирующий код будет загружаться, он не может заменить смещение
текста сообщения константой. Во всем остальном, версия C использует все
приемы описанные для ассемблерого варианта.
Трюком здесь является то, как компилятор и линкер убедили постороить эту
программу. Для достижения этого были использованы две редко применяемые
прагмы компилятора. Прагмы #pragma data_seg и code_seg вполне
стандартные прагмы (они поддерживаются в большинстве компиляторов и
делают одно и тоже) и определяют сегмент - где должны быть размещены
данные и код. В нашем случае данные и код конечно же размещаются в одном
сегменте. Но почему они имеют класс STACK? Это потому что WLINK требует
именованного сегмента который уже существует при сборке исполняемого
модуля.
Теперь ключи компилятора. Ключ -s (отключить проверку стека) вполне
очевиден. Если мы оставим проверку стека это увеличит размер кода и что
гораздо хуже, потянет за собой исполняющую библиотеку C (runtime). Это
без вопросов. Ключ -g управляет группой где будет размещен сегмент кода.
Это должна быть DGROUP, в противном случае линкер не объединит сегменты
данных и кода. К счастью Watcom крайне гибок в этих вопросах.
Ключи линкера сходны с теми, что мы использовали для ассемблерной
версии, за исключанием одного дополнительного. Опция NOD - это
сокращение от NODefaultlibs (нет библиотеки по умолчанию) и отключает
поиск библиотеки по умолчанию (для этого компилятор генерирует
специальную запись в оъектном модуле). Такого же эффекта можно добится
ключом -zl компилятора.
Обнако ключ к успеху заключается в опции start. В ассемблерном варианте
мы использовали директиву END для указания точки старта программы. В C
нет этому эквивалента, однако IBM C поддеживает прагму #pragma entry.
Watcom C не имеет такой прагмы, но позволяет указать точку старта опцией
start=symbol линкера WLINK.
Конечная версия исходного текста на C возможно выглядит более сложной
чем ассемблерная, но все еще не слишком сложной для понимания. Результат
гарантирован многочасовой интенсивной борьбой с компилятором, попытками
найти правильное соотношение между ингредиентами и попытками заставить
компилятор и линкер делать то, что они определенно делать не хотели. И
как это не смешно, но результат борьбы не слишком велик (;-)))), прим.
пер.).
Заключение
Я надеюсь, что это небольшое приключение по минимизации программы
развлекло Вас или как минимум не шокировало. В лучшем случае Вы изучили
некоторые приемы которые Вы сможете применить в своих программах, как
это сделал я. Мораль все истории такова, если Вы потрудитесь достаточно
усердно, то Вы сможете достичь результатов которые Вы и представить не
могли (да-да-да, я знаю что это звучит глупо)! Да, между прочим, если Вы
знаете еще приемы которые можно применить для уменьшения размеров
исполняемых программ, дайте мне знать по адресу MichalN@prodigy.net.
Автор статьи: Michal Necasek
Переводчик: Андрей Породько
Комментарии: Reader 2001-10-03 00:39:27 | I'm waiting for continuation... | Паша 2001-10-03 02:30:24 | а где сам код? хачу продолжения рассказа! | Eugene Gorbunoff 2001-10-06 00:52:59 | На 5-ое октября статья переведена полностью.
Кто-нибудь пробывал делать самую маленькую программу парраллельно с тем, как мы публиковали части перевода? :) Мы специально решили выкладывать эту статью по частям, чтобы было как можно интереснее. | Rinat H. Sadretdinow 2001-10-11 14:32:55 | А где же описание категории ``Custom''? | Eugene Gorbunoff 2001-10-25 13:53:49 | Наблюдается полное отсутствие активности со стороны осевых юзеров. Даже хуже - желание убивать все инициативы и т.п.
| Igor Vanin 2001-10-25 16:15:03 | 2EG:
Ты хочешь сказать, что в категории custom не было никакой активности и никаких результатов?
| Eugene Gorbunoff 2001-10-29 00:23:51 | я хочу сказать, что люди не проявили интереса к статье и поэтому перевод был преостановлен.
Я правильно понял? | Timur Kazimirov 2001-10-30 04:47:58 | Нет, неправильно ;) Я, правда, её ещё в оригинале читал, но всё равно - интересно. | Igor Vanin 2001-10-31 17:38:19 | 5 октября статья была переведена полностью, а 29 октября перевод был приостановлен? Как так? :-)
|
Прокомментируйте эту статью (напоминаем, автор работал над текстом несколько недель, уважайте мнение других).
|
eComStation 2.0 создана для работы на современных компьютерах (i3/i5/i7, Core Duo, AMD X2), но будет работать и на компьютерах, купленных 5 лет назад. Что нового в eCS 2.0? |
|
|
|
Готовая eComStation на SSD диске
Последний активный опрос: Какая высота барьера RPM?
[Google]
|
IBM OS/2 Warp
|