Welcome to eComStation.RU site!

Select your language: Russian English Deutch Spanish Italian Portuguese Czech Polish French

Frequently asked questions and answers:

eComStation.RU

ru · en · de · es · it · pt · cz · pl · fr
eComStation - это совершенно другая операционная система для PC (IBM OS/2 Warp)
Программы, новости, статьи, поддержка пользователей, оборудование, вопросы и ответы.
 
      Что такое OS/2?НовостиУстановкаОбновлениеПрименениеБудущееСообществоКупить    
(Карта сайта)

 
 
Списки протестированного OS/2 оборудования
Как получить драйверы OS/2 бесплатно

 
Обновление

 
Программы

 
(Санкт-Петербург)

 
Преимущества (1)

 
Разработчику (1)

 
(Пайпы программ)

 
Компании: (1)

 
История (1):

 
(Бонусы)

 
Советы:

 
(Барьеры и решения)

 
Технологии: (1)

 
(Применение в науке, лаборатории, ..)

 

 
Готовые решения:

 
Новая eComStation:

 
Будущее: (1)

 
(Ссылки на другие сайты)

 
(Картинка дня)

 
Артефакты OS/2

 
Гаджеты

 

Конкурс миниатюризации для 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

Переводчик: Андрей Породько

Попробуй программу:

eCo Tunes - интернет-радио. Слушай музыку других народов, любых жанров.

Комментарии:

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 октября перевод был приостановлен? Как так? :-)

Прокомментируйте эту статью (напоминаем, автор работал над текстом несколько недель, уважайте мнение других).


Ваше имя:

Ваш E-Mail:

CODE:
......

  

Ваш комментарий:


eCS 2.0 наполнена драйверами для современных устройств. Поддержка современных материнских плат, сетевых адаптеров и видеокарт. Всё настраивается автоматически. Что нового в eCS 2.0?

Статьи

Операционная система
Программное обеспечение
Оборудование
Для разработчика
Разное
Колонка редактора


Готовая eComStation на SSD диске

 





Последний активный опрос: Какая высота барьера RPM?

IBM OS/2 Warp

 
Обучение новичков

Отчет: OS/2 совместимое оборудование

 
Статьи


   
  Почему eComStation?
Возможности
Особенности
Применение
Ролики и скриншоты
   eComStation для
для бизнесменов
для студентов и инженеров
для продавцов компьютеров
сообщество пользователей
   Разработчик
Распространить программу
Описание API, библиотеки
Начать новый проект
Конкурсы
   Программы
Он-лайн каталог
Выбрать через eCo Market
   Служба поддержки
Отправить вопрос
Купить eComStation
Вопросы и ответы
Обучение новичков
 
 
© 2001 - 2014 eCo Software, All rights reserved
eComStation is a registered trademark of Serenity Systems International
OS/2 Warp is a registered trademark of IBM Corporation
 

 

 
Картинка дня: