┌──┌─┐┌──
──┘├─┘──┘ Presents
┐  ┌┐┐┌─┤ VMag, Issue 3, 1 January 1999
└─┘┘ ┘└─┘ ─────────────────────────────
                                                 ╔════════════════════════════╗
                                                 ║     Version: 1.0           ║
                                                 ║     Release: 30-Oct-1998y  ║
                                                 ║  Created by: Hard Wisdom   ║
                                                 ╚════════════════════════════╝

─[30-Oct-1998y]─[v1.0]─────────────────────────────────────────────────────────
 Первоначально написанный файл
───────────────────────────────────────────────────────────────────────────────


                            Самозагрузка NE модулей
                            ~~~~~~~~~~~~~~~~~~~~~~~
                                (краткий обзор)



[0] Прелюдия или грустная история о CAD программах..........................___
[1] Обзор самозагрузки файлов NE............................................___
 [1.1] Функции загрузчика...................................................___
 [1.2] Таблица данных загрузчика............................................___
 [1.3] Загрузка сегментов...................................................___
 [1.4] Подкачка сегментов...................................................___
 [1.5] Работа с железом.....................................................___
[2] Описание используемых функций...........................................___
[3] Описание сопутствующих материалов.......................................___



[0] Прелюдия или грустная история о CAD программах
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Принесли мне однажды CAD программу,  работала  она  неправильно  (записывать
данные не хотела ;-). И  оказался  код  программы  запакован.  Поскольку  я  не
встречал готовых распаковщиков для OptLinker'a (а это был  он),  а  так  же  не
встречал распаковщиков для NE вообще, равно как  и  запакованных  NE  файлов  и
самих  архиваторов  (кроме  PKLITE),  то  самым  логичным  было  написать  свой
распаковщик,  что  я  и  сделал.   В   данном   обзоре   можно   прочесть   про
самозагружающиеся аппликации в формате NE, а так же рассмотреть и  попользовать
сам распаковщик (с сырцом, разумеется).
   За основу брались: OptLinker Loader  выпотрошенный  из  запускаемого  модуля
Accel EDA, а так же невесть откуда взявшиеся выдержки из справочных файлов  BPW
v1.0 (И зачем они в Inprise себя переименовали ?)


[1] Обзор самозагрузки файлов NE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Этот раздел описывает содержимое уникального сегмента,  который  может  быть
найден только в самозагружающихся приложениях формата  NE  (заталкивать  его  в
другие места просто бессмысленно). Этот сегмент содержит 6 функций:  3  из  них
предоставляет разработчик приложения и  3  выдаются  во  временное  пользование
операционной системой ;-) а точнее KRNL386.EXE,  правда  у  кернела  для работы
необходимо импортировать еще одну  функцию  (на  этот  раз  уже  явно).  Данный
сегмент содержит так же таблицу указателей на  все  эти  функции  (естественно,
что все предоставляемое операционной системой представлено  в  виде  указателей
на требуемые функции).
   Подразумеваем, что читающий близко знаком  со  структурой  NE  файлов  и  не
заостряем внимание на этих мелочах.


[1.1] Функции загрузчика
~~~~~~~~~~~~~~~~~~~~~~~~
   Обычно 16-битовые приложения загружаются кернелом  (16-битовым  естественно,
про 32-бита вообще  сейчас  не  говорим),  который  считывает  их  в  память  и
передает управление куда следует ;-) Однако некоторым этого  оказывается  мало,
они хотят сами выполнять данную работу. Безусловно, общий формат  файла  NE  не
меняется, но некоторые детали загрузки  приложения  могут  быть  переложены  на
плечи самого приложения (например  кто-то  захочет  реализовать  упаковку  тела
программ или подгрузку оверлеев (а почему бы и нет?) или некоторый  специальный
механизм распределения ОЗУ под программу).
   Указание на то,  что  приложения  самозагружающееся  находится  в  заголовке
файла, 11-бит (800h маска) определяет  это.  Бит  =  1  -  самозагрузка,  иначе
обычная программа, т.е. в этом  случае  операционная  система  трактует  данный
файл как обычный NE файл и игнорирует специальный сегмент  загрузчика  (который
может использоваться обычным способом).


[1.2] Таблица данных загрузчика
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Итак, первый  сегмент  самозагружающегося  приложения  содержит  специальную
таблицу   в   которой   находится     служебная     информация,     позволяющая
пользовательскому загрузчику (находящемуся там же)  выполнить  свою  работу.  В
этой  таблице  находятся  в  основном  дальние  указатели  16:16   на   функции
участвующие в процессе загрузки. Формат таблички изложен ниже:
╒══════════╤══════════════════════════════════════════════════════════════════╕
│ Смещение │ Описание                                                         │
├──────────┼──────────────────────────────────────────────────────────────────┤
│  00h     │ Номер версии формата файла, должен быть равен "A0" т.е. 3041h    │
│  02h     │ Зарезервировано, тут обычно 0-ли находятся                       │
│  04h  D  │ Указывает на стартовую процедуру загрузки (от разработчика).     │
│          │ В текстве эта процедура будет иметь название - BootApp(...)      │
│  08h  D  │ Указывает на процедуру загрузки сегмента (от разработчика).      │
│          │ В текстве эта процедура будет иметь название - LoadAppSeg(...)   │
│  0Ch     │ Зарезервировано ;-) Хе-хе, операционная система все-таки выс-    │
│          │ тавляет какой-то осмысленный адрес, надо бы его испытать...      │
│  10h  O  │ Указывает на процедуру отведения памяти, предоставляемую ОС.     │
│          │ В текстве эта процедура будет иметь название - MyAlloc(...)      │
│  14h  O  │ Процедура поиска экспортируемых точек входа                      │
│          │ В текстве эта процедура будет иметь название - FindOrdinal()     │
│  18h  D  │ Указатель на процедуру завершения (от разработчика).             │
│          │ В текстве эта процедура будет иметь название - ExitApp(...)      │
│  1Ch     │ Зарезервировано, скорее всего так и есть.                        │
│  1Eh     │ Зарезервировано, скорее всего так и есть.                        │
│  20h     │ Зарезервировано, скорее всего так и есть.                        │
│  22h     │ Зарезервировано. (какой-то глюк, поле то используется!)          │
│  24h  O  │ Указывает на процедуру фиксации владельца, предоставляемую ОС.   │
│          │ В текстве эта процедура будет иметь название - SetOwner(...)     │
╞══════════╧══════════════════════════════════════════════════════════════════╡
│ D - разработчик (i.e. developer), O - операционная система (далее ОС)       │
╘═════════════════════════════════════════════════════════════════════════════╛
Все  указатель  в  этой   табличке   (предоставляемые   разработчиком)   должны
находиться в данном сегменте, дополнительную корректировку переходов  загрузчик
ОС не позволяет (? а может все-таки побаловаться).

   После  создания  базы  данных  модуля  (в  большинстве  своем  совпадает   с
заголовком NE файла, будет активно использоваться  самим  загрузчиком)  таблица
сегментов содержит селекторы (хендлы) для загружаемых сегментов.  Считайте  это
последним подарком ОС, все остальное ложится на ваши плечи.

   Код загрузчика располагается в самом первом сегменте NE файла  (сразу  вслед
за "A0" табличкой). В задачи загрузчика входит подкачка сегментов приложения  и
дополнительная работа с железом.


[1.3] Загрузка сегментов
~~~~~~~~~~~~~~~~~~~~~~~~
   ОС вызывает функцию  BootApp  (смещение  4h)  предоставляемую  разработчиком
приложения. Функция отводит память для  всех  сегментов  приложения  отмеченных
флагами  PRELOAD  или  FIXED  вызовом  специально  предоставляемой  ОС  функции
MyAlloc (смещение 10h). Загрузка  сегментов  обычно  производится  при  участии
функции  LoadAppSeg  (предоставляемой  разработчиком),  окончательную  фиксацию
сегментов осуществляет функция предоставляемая ОС -  SetOwner  (смещение  24h).
(данная функция увязывает отведенную память  с  базой  данных  модуля).  Первый
сегмент, который должен создать  BootApp()  -  автоматический  сегмент  данных.
Этот сегмент, собственно  говоря,  по  совместительству  содержит  еще  и  стек
приложения.   Этот   сегмент   необходим   для   корректной   работы    функции
PatchCodeHandle (операционной системы).


[1.4] Подкачка сегментов
~~~~~~~~~~~~~~~~~~~~~~~~
   В  дополнение  к  загрузке  сегментов  LoadAppSeg()  перезагружает  сегменты
выброшенные операционной системой из памяти. Поскольку на  плечи  этой  функции
возложена задача подкачки сегментов - эта функция обязана  корректно  управлять
битами 1 и 2 поля флагов  в  таблице  сегментов.  Документация  не  рекомендует
никому выполнять корректировки в базе данных  задачи  (кроме  данной  функции).
Но что нам запреты ? Ж-) Бит 1 указывает на отведение  памяти  для  сегмента  и
бит 2 указывает состояние сегмента (загружен/нет). Собственно, это не  описание
NE формата. Все остальное (и более подробно) можно найти там.
   Если загрузчик отвел память для сегмента, но  сегмент  не  загружен  (бит  1
установлен, а бит 2 нет),  то  LoadAppSeg()  должен  с  помощью  GlobalHandle()
проверить фактическое состояние дел с памятью для сегмента, и  если  память  не
отведена, то вызовом  GlobalReAlloc()  необходимо  перезатребовать  память  для
сегмента.
   Наконец память уже отведена, тогда LoadAppSeg() должна вычитать  сегмент  из
файла и вызвать PatchCodeHandle()  для  корректировки  пролога  каждой  функции
расположенной в подгруженном сегменте. После корректировки прологов  необходимо
восстановить  все  дальние  ссылки  внутри  этого  сегмента.   Для   указателей
определяемых ординалами необходимо вызовом  FindOrdinal()  вычислить  требуемый
адрес.

[1.5] Работа с железом
~~~~~~~~~~~~~~~~~~~~~~
   При выходе из  самозагруженного  приложения  операционная  система  вызывает
функцию ExitApp(). На эту функцию возлагается сброс  всего  попользованного  за
время работы железа, как правило эта функция  состоит  из  тривиальной  команды
RetF (0CBh). Очистку памяти, закрытие файлов и прочую грязную работу  берет  на
себя операционная система.


[2] Описание используемых функций
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

BootApp()
~~~~~~~~~
void BootApp(hBlock, hFile) - загрузка приложения.

 hBlock      Определяет селектор сегмента базы данных модуля,
             ее формат очень близок к формату NE заголовка файла.

 hFile       Валидный файловый хендл MS-DOS, определяет запускаемый файл из
             которого загружается приложение.

   База данных модуля содержит много  интересной  информации,  как  то:  версия
линкера, таблица  сегментов,  размер  и  местоположение  всевозможных  структур
внутри файла модуля, указатели на кучу и стек и их (структур данных) размер.


FindOrdinal()
~~~~~~~~~~~~~
DWORD FindOrdinal(hBlock, wEntryNo)

 hBlock      Определяет селектор сегмента базы данных модуля,
             ее формат очень близок к формату NE заголовка файла.

 wEntryNo    Указывает индекс в таблице точек входа модуля, процедура
             вернет адрес для соответствующей точки входа.

   В случае  успеха  будет  возвращен  адрес  соответствующей  точки  входа,  в
противном случае результат 0. Собственно параметр wEntryNo так же известен  под
названием ординал.


ExitApp()
~~~~~~~~~
void ExitApp(hBlock)  - завершает самозагруженное приложение.

 hBlock      Определяет селектор сегмента базы данных модуля,
             ее формат очень близок к формату NE заголовка файла.


MyAlloc()
~~~~~~~~~
DWORD MyAlloc(wFlags, wSize, wElem)
  отводит память для сегмента в самозагруженном приложении.

 wFlags  Указывает на флаги создаваемого сегмента.
 wSize   Это размер элемента в байтах.
 wElem   И количество элементов в сегменте.

   Младшее  слово  результата  содержит  хендл  сегмента  в  случае   успешного
выполнения, старшее слово содержит селектор. (Однако, в  случае,  если  функция
отведет только хендл для сегмента,  то  младшее  слово  будет  содержать  0,  а
старшее - сам хендл). В противном случае  возвращаемое  значение  будет  0  для
младшего и старшего слов.

   Флаги,   определяемые   параметром   wFlags   являются   флагами   сегмента,
находящимися в таблице сегментов размещенной  непосредственно  после  основного
информационного  блока  в  базе  данных  модуля.  Операционная   система   сама
преобразовывает wFlags в соответствующие значения перед вызовом GlobalAlloc().
   Размер сегмента в байтах, получается сдвигом  влево  значения  указанного  в
wSize параметре на число бит определяемых параметром wElem.


PatchCodeHandle()
~~~~~~~~~~~~~~~~~
void PatchCodeHandle(hSeg) - модифицирует пролог функции.

 hSeg   Определяет сегмент содержащий точки кода подлежащие модификации.

   Эта функция -  одна  из  4-х  предоставляемых  ядром  операционной  системы.
Приложение может обращаться к этой функции  обычным  механизмом  импорта,  т.е.
создав соответствующий DEF файл и записав в его секции IMPORTS:

PatchCodeHandle = KERNEL.110

Если  точка  входа  в  сегменте  использует  регистр  DS,   то   пролог   будет
модифицирован следующим образом:

   Оригинальный пролог       Модифицированный пролог
   ~~~~~~~~~~~~~~~~~~~       ~~~~~~~~~~~~~~~~~~~~~~~
      push ds                     mov ax, dgroup
      pop ax
      nop

Если точка входа не использует DS регистр, то пролог будет модифицирован  таким
образом:

   Оригинальный пролог       Модифицированный пролог
   ~~~~~~~~~~~~~~~~~~~       ~~~~~~~~~~~~~~~~~~~~~~~
      push ds                     mov ax, ds
      pop ax                      nop
      nop

Загрузчик вызывает функцию PatchCodeHandle() из функции  LoadAppSeg(),  которая
перезагружает выброшенный из памяти сегмент.  Перед  вызовом  PatchCodeHandle()
необходимо вызвать SetOwner() для установки владельца  сегмента.  Дополнительно
загрузчик должен установить бит SINGLEDATA в информационном блоке  базы  данных
модуля.


LoadAppSeg()
~~~~~~~~~~~~
WORD LoadAppSeg(hBlock, hFile, wSegID)

Эта функция загружает сегмент в первый раз или подкачивает удаленные из  памяти
сегменты.   Сегмент   опрелеояется   параметром   wSegID   и    относится     к
соответствующему приложению.

 hBlock      Определяет селектор сегмента базы данных модуля,
             ее формат очень близок к формату NE заголовка файла.

 hFile       Определяет хендл файла MS-DOS содержащего приложение (равен -1
             для не открытого файла).

 wSegID      Определяет сегмент необходимый для перезагрузки.

   Возвращаемое значение есть селектор для сегмента в случае  успеха  или  0  в
противном  случае.  Краткое  описание  hBlock  параметра  приведено  в  функции
BootApp(). Третий параметр, wSegID, определяется линкером во время компоновки.


SetOwner()
~~~~~~~~~~
void SetOwner(hSel, hOwner)

Эта функция связывает отдельно взятый сегмент с загруженным приложением.

 hSel        Селектор или хендл определяющий сегмент для связывания с
             загруженным модулем

 hOwner      Информационный блок в базе данных модуля к которому
             привязывается сегмент

Описание базы данных модуля приведено выше, полное  описание  можете  прочитать
например у М.Питрека "Windows Internals"
   После  того,  как  операционная  система  отводит  память  для  сегмента   с
использованием MyAlloc() она  вызывает  SetOwner(),  теперь  эта  "возможность"
доступна и вам ;-)


[3] Описание сопутствующих материалов
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   К  этому  описанию  прилагаются  файлы  OPT_P1.(BIN,IDB)  являющиеся   рипом
распаковщика от OptLinker'a Все желающие могут восполнить  недостающие  пробелы
в описании покопавшись в данном первоисточнике. А так же  сабж,  ради  которого
все и делалось - распаковщик UNOPT.(EXE,C) и (UNOPTRIP.ASM)

Любители ковырять сырцы да вспомнят об их происхождении...

           Home Page: www.geocities.com/SiliconValley/Hills/7827
              E-Mail:         [email protected]