Глава 7 - Загрузка и выполнение программ в DOS, структура EXE и COM программ
Мы используем cookie-файлы, чтобы получить статистику, которая помогает нам улучшить сервис для Вас с целью персонализации сервисов и предложений. Вы можете прочитать подробнее о cookie-файлах или изменить настройки браузера. Продолжая пользоваться сайтом без изменения настроек, вы даёте согласие на использование ваших cookie-файлов.
speech bubble

Глава 7 - Загрузка и выполнение программ в DOS, структура EXE и COM программ

Самоучитель по языку ассемблер(assembler)

Содержание:

 

ВНИМАНИЕ! Данный самоучитель был написан много лет назад и его автор не сможет ответить на ваши вопросы. Курс по assembler предназначен для саморазвития. Если у вас хватает квалификации, можете предложить внести правки.

 

Загрузка и выполнение программ

При загрузке программ в оперативную память DOS (дисковая операционная система) инициализирует как минимум три сегментных регистра: CS, DS и SS. При этом совокупности байтов, представляющих команды процессора (код программы), и данные помещаются из файла на диске в оперативную память, а адреса этих сегментов записываются в CS и DS соответственно. Сегмент стека либо выделяется в области, указанной в программе, либо совпадает (если он явно в программе не описан) с самым первым сегментом программы. Адрес сегмента стека помещается в регистр SS. Программа может иметь несколько кодовых с сегментов и сегментов данных и в процессе выполнения специальными командами выполнять переключения между ними. Для того чтобы адресовать одновременно два сегмента данных, например, при выполнении операции пересылки из одной области памяти в другую, можно использовать регистр дополнительного сегмента ES. Кодовый сегмент и сегмент стека всегда определяются содержимым своих регистров (CS и SS), и поэтому в каждый момент выполнения программы всегда используется какой-то один кодовый сегмент и один сегмент стека. Причем если переключение кодового сегмента – довольно простая операция, то переключать сегмент стека можно только при условии четкого представления логики работы программы со стеком, иначе это может привести к зависанию системы.

Все сегменты могут использовать различные области памяти, а могут частично или полностью перекрываться. Кодовый сегмент должен обязательно описываться в программе, все остальные сегменты могут отсутствовать. В этом случае DOS при загрузке программы в оперативную память инициирует регистры DS и ES значением адреса префикса программного сегмента PSP (Program Segment Prefics) – специальной области оперативной памяти размером 256 (100h) байт. PSP может использоваться в программе для определения имен файлов и параметров из командной строки, введенной при запуске программы на выполнение, объема доступной памяти, переменных окружения системы и т.д. Регистр SS при этом инициализируется значением сегмента, находящегося сразу за PSP, т.е. первого сегмента программы. При этом необходимо учитывать, что стек «растет вниз» (при помещении в стек содержимое регистра SP, указывающего на вершину стека, уменьшается, а при считывании из стека – увеличивается). Таким образом, при помещении в стек каких-либо значений они могут затереть PSP и программы, находящиеся в младших адресах памяти, что может привести к непредсказуемым последствиям. Поэтому рекомендуется всегда явно описывать сегмент стека в тексте программы, задавая ему размер, достаточный для нормальной работы.

Вот пример размещения сегментов в памяти:

Загрузка и выполнение программ

Рассмотрим распределение памяти на примере простейшей структуры программы:

;Данные программы
DATA SEGMENT
MSG DB ‘Текст$’
DATA ENDS
;Код программы
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
;команды установки сегмента данных
MOV AX,DATA
MOV DS,AX
................
;команды
CODE ENDS
END START

В этой программе явно описаны два сегмента – кода с именем CODE и данных с именем DATA. Директива ASSUME связывает имена этих сегментов, которые в общем случае могут быть произвольными, с сегментными регистрами CS и DS соответственно.
Распределение памяти при загрузке программы на исполнение показано на рисунке:

Загрузка и выполнение программ

Как видно из рисунка, сегмент стека в данном случае установлен на PSP, что при его интенсивном использовании может привести к неожиданным результатам. После инициализации в регистре IP находится смещение первой команды программы относительно начала кодового сегмента, адрес которого помещен в регистр CS. Процессор, считывая эту команду, начинает выполнение программы, постоянно изменяя содержимое регистра IP и при необходимости CS для получения кодов очередных команд до тех пор, пока не встретит команду завершения программы. DS после загрузки программы установлен на начало PSP, поэтому для его использования в первых двух командах программы выполняется загрузка DS значением сегмента данных. Вообще говоря структура приведена здесь для примера, а более подробно мы рассмотрим её позднее.

 

Различия EXE и COM программ

DOS может загружать и выполнять программные файлы двух типов – COM и EXE.

Ввиду сегментации адресного пространства процессора 8086 и того факта, что переходы (JMP) и вызовы (CALL) используют относительную адресацию, оба типа программ могут выполняться в любом месте памяти. Программы никогда не пишутся в предположении, что они будут загружаться с определенного адреса (за исключением некоторых самозагружающихся, защищенных от копирования программ).

Файл COM-формата – это двоичный образ кода и данных программы. Такой файл должен занимать менее 64K и не содержать перемещаемых адресов сегментов.

Файл EXE-формата содержит специальный заголовок, при помощи которого загрузчик выполняет настройку ссылок на сегменты в загруженном модуле.

Перед загрузкой COM- или EXE-программы DOS определяет сегментный адрес, называемый префиксом программного сегмента (PSP), как базовый для программы. Затем DOS выполняет следующие шаги:

  1. создает копию текущего окружения DOS (область памяти, содержащая ряд строк в формате ASCIIZ, которые могут использоваться приложениями для получения некоторой системной информации и для передачи данных между программами) для программы;
  2. помещает путь, откуда загружена программа, в конец окружения;
  3. заполняет поля PSP информацией, полезной для загружаемой программы (количество памяти, доступное программе; сегментный адрес окружения DOS; текущие векторы прерываний INT 22H INT 23H и INT 24H и т.д).

EXE-программы.

EXE-программы содержат несколько программных сегментов, включая сегмент кода, данных и стека. EXE-файл загружается, начиная с адреса PSP:0100h. В процессе загрузки считывается информация заголовка EXE в начале файла и выполняется перемещение адресов сегментов.

Это означает, что ссылки типа
mov ax,data_seg
mov ds,ax
и
call my_far_proc

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

После перемещения управление передается загрузочному модулю посредством инструкции далекого перехода (FAR JMP) к адресу CS:IP, извлеченному из заголовка EXE.

В момент получения управления программой EXE -формата:

  1. DS и ES указывают на начало PSP
  2. CS, IP, SS и SP инициализированы значениями, указанными в заголовке EXE
  3. поле PSP MemTop (вершина доступной памяти системы в параграфах) содержит значение, указанное в заголовке EXE. Обычно вся доступная память распределена программе.

COM-программы.

COM-программы содержат единственный сегмент (или, во всяком случае, не содержат явных ссылок на другие сегменты). Образ COM-файла считывается с диска и помещается в память, начиная с PSP:0100h. В общем случае, COM-программа может использовать множественные сегменты, но она должна сама вычислять сегментные адреса, используя PSP как базу.

COM-программы предпочтительнее EXE-программ, когда дело касается небольших ассемблерных утилит. Они быстрее загружаются, ибо не требуется перемещения сегментов, и занимают меньше места на диске, поскольку заголовок EXE и сегмент стека отсутствуют в загрузочном модуле.

После загрузки двоичного образа COM-программы:

  1. CS, DS, ES и SS указывают на PSP;
  2. SP указывает на конец сегмента PSP (обычно 0FFFEH, но может быть и меньше, если полный 64K сегмент недоступен);
  3. слово по смещению 06H в PSP (доступные байты в программном сегменте) указывает, какая часть программного сегмента доступна;
  4. вся память системы за программным сегментом распределена программе;
  5. слово 00H помещено (PUSH) в стек.
  6. IP содержит 100H (первый байт модуля) в результате команды JMP PSP:100H.

 

Структура программ на ассемблере

Следует отметить, что какой-либо фиксированной структуры программы на языке Ассемблера нет, но для небольших EXE-программ с трехсегментной структурой типична следующая структура:

;Определение сегмента стека
STAK SEGMENT STACK
DB 256 DUP (?)
STAK ENDS
;Определение сегмента данных
DATA SEGMENT
SYMB DB '#' ;Описание переменной с именем SYMB
;типа Byte и со значением «#»
. . . ;Определение других переменных
DATA ENDS
;Определение сегмента кода
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STAK
;Определение подпрограммы
PROC1 PROC
. . . ;Текст подпрограммы
PROC1 ENDP
START: ;Точка входа в программу START
XOR AX,AX
MOV BX, data ;Обязательная инициализация
MOV DS, BX ;регистра DS в начале программы
CALL PROC1 ;Пример вызова подпрограммы
. . . ;Текст программы
MOV AH,4CH ;Операторы завершения программы
INT 21H
CODE ENDS
END START

 

В общем случае, взаимное расположение сегментов программы может быть любым, но чтобы сократить в командах число ссылок вперед и избежать проблем с префиксами для них, рекомендуется сегмент команд размешать в конце текста программы.
Сегмент стека в приведенной структуре описан с параметром STACK, поэтому в самой программе нет необходимости загружать сегментный регистр SS. Сегментный регистр CS тоже нет необходимости загружать, как уже отмечалось ранее. В связи с этим в начале программы загружается лишь регистр DS.

Относительно сегмента стека нужно сделать следующее замечание. Даже если сама программа не использует стек, описывать в программе сегмент стека все равно надо. Дело в том, что стек программы использует операционная система при обработке прерываний.
Необходимо также заметить, что все предложения, по которым ассемблер заносит что-либо в формируемую программу (инструкции, директивы определения данных и т.д.) обязательно должны входить в какой-либо программный сегмент, размещать их вне программных сегментов нельзя. Исключение составляют директивы информационного характера, например, директивы EQU, директивы описания типов структур и записей. Кроме того, не рекомендуется размещать в сегменте данных инструкции, а в сегменте кода – описание переменных из-за возникающих в этом случае проблем с сегментированием.

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

;Определение сегмента кода
CODE SEGMENT
ASSUME CS:CODE, DS:CODE, SS:CODE
ORG 100H ;Начало необходимое для COM-программы
;Определение подпрограммы
PROC1 PROC
. . . ;Текст подпрограммы
PROC1 ENDP
START:
. . . ;Текст программы
MOV AH, 4CH ;Операторы завершения программы
INT 21H
;===== Data =====
BUF DB 6 ;Определение переменной типа Byte
. . . ;Определение других переменных
CODE ENDS
END START

Надо отметить что кроме структуры различие между этими форматами программ состоит еще и в режимах компиляции, то есть в том какие параметры указывать в командной строке! Это мы разберём на практике! А теперь теже самые структуры программ но с упрощенными директивами!

Вот как компактнее можно описать EXE программу

.model small
.code ;начали сегмент кода
start:
;текст программы
.data ;начали сегмент данных
message db 'This symbol is out:',0Ah,0Dh,'$'
end start

Видите те самые директивы распределения сегментов? В начале просто задали модель памяти, а затем где какие сегменты! :) так гораздо проще! Именно этот подход я и буду демонстрировать при разборе конкретных задач.

COM программа

.model tiny
.code
org 100h
start:
;текст программы
ret;так можно завершать COM программу!!!
message db 'This string is out!!!!!!', 0Ah, 0Dh,'$'
end start

Здесь еще проще! Всё в одном сегменте! и код и данные! Только модель памяти задали tiny да добавили директиву изменения программного счётчика - ведь COM программа грузится после PSP размеров в 100h.

 

Для комментирования необходимо авторизоваться