Связь с администрацией сайта:       

demo

Среди толпы я одинок

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

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

Вот пример размещения сегментов в памяти:
 pr razm seg
Рассмотрим распределение памяти на примере простейшей структуры программы:
;Данные программы
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 соответственно. 
Распределение памяти при загрузке программы на исполнение показано на рисунке:
 primer seg2
Как видно из рисунка, сегмент стека в данном случае установлен на 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.
 
 
Голосуй
(1 Голос)
Оставьте комментарий

Поля, отмеченные звездочкой(*) обязательны для заполнения. HTML теги не приветствуются.

Вход на сайт