;Чйсло шагов в цикле
fill: |
;Отправим число в массив
;Инкремент числа-заполнителя
; Смещение в массиве к следующему слову
;На метку fill (CX раз)
Повышение эффективности достигается за счет того, что команда занесения числа в элемент массива оказывается короче (так как в неё не входит адрес массива) и выполняется быстрее, так как этот адрес не надо каждый раз считывать из памяти.
Базово-индексная адресация со смещением. Адресуется память (байт или слово). Относительный адрес операнда определяется как сумма содержимого двух регистров и смещения.
Это способ адресации является развитием предыдущего. В нем используются те же пары регистров, но полученный с их помощью результирующий адрес можно еще сместить на значение указанной в команде константы. Как и в случае базово-индексной адресации, константа может представлять собой индекс (и тогда в одном из регистров должен содержаться базовый адрес памяти), но может быть и базовым адресом. В последнем случае регистры могут использоваться для хранения составляющих индекса. Приведем формальный пример рассматриваемого режима адресации.
Пусть в сегменте данных определен массив из 24 байтов, в котором записаны коды латинских и русских символов верхнего ряда клавиатуры:
Sims db "QWERTYUIOPO"
db "ЙЦУКЕНГШЩЗХЪ"
Последовательность команд
;Число байтов в строке
mov BX,12
mov SI,6
mov DL,syms[BX][SI]
загрузит в регистр DL элемент с индексом 6 из второго ряда, т.е. код ASCII буквы Г. Тот же результат можно получить, загрузив в один из регистров не индекс, а адрес массива:
mov BX,offset sym
mov SI, 6
mov DL,12[BX][SI]
Глава 2
Основы программирования
2.6. Переходы
Как уже отмечалось в гл. 1, присущий процессору алгоритм выполнения программы заставляет его выполнять команды программы друг за другом, в том порядке, как они были описаны в исходном тексте программы и содержатся в выполнимом модуле. Однако часто программисту требуется нарушить этот порядок, заставив процессор обойти некоторый участок программы, перейти на выполнение другой ветви или передать управление подпрограмме, имея в виду после ее завершения вернуться на прежнее место. Все эти операции осуществляются с помощью команд переходов. Переходы разделяются на безусловные, когда передача управления в другую точку программы осуществляется в безусловном порядке, независимо ни от каких обстоятельств, и условные, осуществляемые или не осуществляемые в зависимости от тех или иных условий: результатов сравнения, анализа, поиска и т.п. Безусловные переходы подразделяются на собственно переходы (без возврата в точку перехода) и вызовы подпрограмм (с возвратом после завершения подпрограммы).
Операции переходов и вызовов подпрограмм, помимо их практической ценности, представляют значительный методический интерес, так как затрагивают основу архитектуры процессора — сегментную адресацию памяти. Многочисленные разновидности команд переходов и вызовов обязаны своим существованием не столько потребностям практического программирования, сколько принципиальным архитектурным особенностям процессора. Отчетливое понимание этих особенностей и, соответственно, условий применения и возможностей рахчичных операций переходов необходимо не только при использовании языка ассемблера, но и при программировании на языках высокого уровня, где иногда используется несколько иная терминология, но существо дела остается без изменения.
Безусловные переходы осуществляются с помощью команды jmp, которая может использоваться в 5 разновидностях. Переход может быть:
прямым коротким (в пределах -128...+127 байтов);
прямым ближним (в пределах текущего сегмента команд);
прямым дальним (в другой сегмент команд);
косвенным ближним (в пределах текущего сегмента команд через ячейку
с адресом перехода);
косвенным дальним (в другой сегмент команд через ячейку с адресом
перехода).
Рассмотрим последовательно структуру программ с переходами разного вида.
Прямой короткий (short) переход. Прямым называется переход, в команде которого в явной форме указывается метка, на которую нужно перейти. Разумеется, эта метка должна присутствовать в том же программном сегменте, при этом помеченная ею команда может находиться как до, так и после команды jmp. Достоинство команды короткого перехода заключается в том, что она занимает лишь 2 байт памяти: в первом байте записывается код операции (EBh), во втором — смещение к точке перехода. Расстояние до точки перехода отсчитывается от очередной команды,
т.е. команды, следующей за командой jmp. Поскольку требуется обеспечить переход как вперед, так и назад, смещение рассматривается, как число со знаком и, следовательно, переход может быть осуществлен максимум на 127 байт вперед или 128 байт назад. Прямой короткий переход оформляется следующим образом:
code segment
jmp short go;Код ЕВ dd
go:
code ends
Если программа транслируется ассемблером TASM, и в строке вызова транслятора указано, что трансляция следует выполнить в два прохода
tasm /m2 p,p,p
то описатель short можно опустить, так как ассемблер сам определит, что расстояние до точки перехода укладывается в короткий переход, даже если метка go расположена после строки с командой jmp. При использовании транслятора MASM указание описателя short обязательно (если метка go расположена после команды jmp). Здесь проявляются незначительные различия ассемблеров разных разработчиков.
В комментарии указан код команды; dd (от displacement, смещение) обозначает байт со смещением к точке перехода от команды, следующей за командой jmp.
При выполнении команды прямого короткого перехода процессор прибавляет значение байта dd к младшему байту текущего значения указателя команд IP (который, как уже говорилось, всегда указывает на команду, следующую за выполняемой). В результате в IP оказывается адрес точки перехода, а предложения, находящиеся между командой jmp и точкой перехода, не выполняются. Между прочим, конструкция с прямым переходом вперед часто используется для того, чтобы обойти данные, которые по каким-то причинам желательно разместить в сегменте команд.
Прямой ближний (near), или внутрисегментный переход. Этот переход отличается от предыдущего только тем, что под смещение к точке перехода отводится целое слово. Это дает возможность осуществить переход в любую точку 64-кбайтного сегмента.
code segment
jmp go;Код Е9 dddd
go:
code ends
Глава 2
Основы программирования
Метка go может находиться в любом месте сегмента команд, как до, так и после команды jmp. В коде команды dddd обозначает слово с величиной относительного смещения к точке перехода от команды, следующей за командой jmp.
При выполнении команды прямого ближнего перехода процессор должен прибавить значение слова dddd к текущему значению указателя команд IP и сформировать тем самым адрес точки перехода. Что представляет собой смещение dddd? Какая это величина, со знаком или без знака' Если рассматривать смещение как величину без знака, то переход будет возможен только вперед, что, конечно, неверно. Если же смещение является величиной со знаком, то переход возможен не более, чем на полсегмента вперед или на полсегмента назад, что тоже неверно. В действительности, рассматривая вычисление адреса точки перехода, следует иметь в виду явление оборачивания, суть которого можно кратко выразить такими соотношениями:
FFFFh+0001h=OOOOh
OOOOh-0001h=FFFFh j
i
Если последовательно увеличивать содержимое какого-либо регистра или ячейки памяти, то, достигнув верхнего возможного предела FFFFh, число «перевалит» через эту границу, станет равным нулю и продолжит нарастать в области малых положительных чисел (1, 2, 3, и т.д.). Точно так же, если последовательно уменьшать некоторое положительное число, то оно, достигнув нуля, перейдет в область отрицательных (или, что то же самое, больших беззнаковых) чисел, проходя значения 2, 1, О, FFFFh, FFFEh и т.д.
Таким образом, при вычислении адреса точки перехода смещение следует считать числом без знака, но при этом учитывать явление оборачивания. Если команда jmp находится где-то в начале сегмента команд, а смещение имеет величину порядка 64 К, то переход произойдет вперед, к концу сегмента. Если же команда находится в конце сегмента команд, а смещение имеет ту же величину порядка 64 К, то для определения точки перехода надо двигаться по сегменту вперед, дойти до его конца и продолжать перемещаться от начала сегмента по-прежнему вперед, пока не будет пройдено заданное в смещении число байтов. Для указанных условий мы попадем в точку, находящуюся недалеко от команды jmp со стороны меньших адресов.
Итак, с помощью команды ближнего перехода (команда jmp без каких-либо спецификаторов) можно перейти в любую точку в пределах данного сегмента команд. Для того, чтобы перейти в другой сегмент команд, следует воспользоваться командой дальнего перехода.
Прямой дальний (far), или межсегментный переход. Этот переход позволяет передать управление в любую точку любого сегмента. При этом, очевидно, предполагается, что программный комплекс включает несколько сегментов команд. Команда дальнего перехода имеет длину 5 байт и включает, кроме кода операции EAli, еще и полный адрес точки перехода, т.е.
сегментный адрес и смещение. Транслятору надо сообщить, что этот переход — дальний (по умолчанию команда jmp транслируется, как команда ближнего перехода). Это делается с помощью описателя far ptr, указываемого перед именем точки перехода.
codel segment
assume CS:codel;Сообщим транслятору, что это сегмент команд
jmp far ptr go;Код EA dddd ssss
codel ends
code 2 segment
assume CS:code2;Сообщим транслятору, что это сегмент команд
go:
code2 ends
Метка go находится в другом сегменте команд этой двухсегментной программы. В коде команды ssss — сегментный адрес сегмента code2, a dddd — смещение точки перехода go в сегменте команд code2.
Заметим, что при наличии в программе нескольких сегментов команд, каждый из них необходимо предварять директивой ассемблера assume СВ:1шя_сегмента, которая сообщает транслятору о том, что начался очередной сегмент команд. Это поможет транслятору правильно обрабатывать адреса меток, встречающихся в этом сегменте.
Освоив применение команд дальних переходов, мы получили возможность создавать программы любой длины. Действительно, предусмотрев в конце каждого программного сегмента команду дальнего перехода на начало следующего, мы можем включить в программный комплекс любое число сегментов по 64 Кбайт. Единственное ограничение — чтобы они все поместились в памяти. В действительности так, конечно, не делают. Разумнее дополнительные сегменты команд заполнить подпрограммами и вызывать их из основного сегмента (или друг из друга) по мере необходимости. Однако и в этом случае команды вызовов подпрограмм должны быть дальними. Разновидности подпрограмм и команд их вызова будут рассмотрены ниже.
Все виды прямых переходов требуют указания в качестве точки перехода программной метки. С одной стороны, это весьма наглядно; просматривая текст программы, можно сразу определить, куда осуществляется переход. С другой стороны, такой переход носит статический характер — его нельзя настраивать по ходу программы. Еще более серьезный недостаток прямых переходов заключается в том, что они не дают возможность Перейти по известному абсолютному адресу, т.е. не позволяют обратиться ни к системным средствам, ни вообще к другим загруженным в память Программам (например, резидентным). Действительно, программы операционной системы не имеют никаких меток, так как метка — это атри-
Глава 2
Основы^рограттрования
бут исходного текста программы, а программы операционной системы транслировались не нами и присутствуют в компьютере только в виде выполнимых модулей. А вот адреса каких-то характерных точек системных программ определить можно, хотя бы из векторов прерываний. Для обращения по абсолютным адресам надо воспользоваться командами косвенных переходов, которые, как и прямые, могут быть ближними и дальними. Косвенный ближний (внутрисегментный) переход. Б отличие от команд прямых переходов, команды косвенных переходов могут использовать различные способы адресации и, соответственно, иметь много разных вариантов. Общим для них является то, что адрес перехода не указывается явным образом в виде метки, а содержится либо в ячейке памяти, либо в одном из регистров, Это позволяет при необходимости модифицировать адрес перехода, а также осуществлять переход по известному абсолютному адресу. Рассмотрим случай, когда адрес перехода хранится в ячейке сегмента данных. Если переход ближний, то ячейка с адресом состоит из одного слова и содержит только смещение к точке перехода.
code segment
jmp DS:go_addr;Код FF 26 dddd
go:;Точка перехода
code ends data segment
go_addr dw go;Адрес перехода (слово) data ends
Точка перехода go может находиться в любом месте сегмента команд. В коде команды dddd обозначает относительный адрес слова go_addr в сегменте данных, содержащем эту ячейку.
В приведенном фрагменте адрес точки перехода в слове go_addr задан однозначно указанием имени метки go. Такой вариант косвенного перехода выполняет фактически те же функции, что и прямой (переход по единственному заданному заранее адресу), только несколько более запутанным образом. Достоинства косвенного перехода будут более наглядны, если представить, что ячейка go_addr поначалу пуста, а по ходу выполнения программы в нее, в зависимости от каких-либо условий, помещается адрес той или иной точки перехода:
mov mov mov |
go_addr,gol go_addr,go2 go_addr,go3
Разумеется, приведенные выше команды должны выполняться не друг за другом, а альтернативно. В этом случае создается возможность перед выполнением перехода определить или даже вычислить адрес перехода, требуемый в данных условиях.
Ассемблер допускает различные формы описания косвенного перехода через ячейку сегмента данных:
jmp DS:go_addr jmp word ptr go_addr jmp go_addr
В первом варианте, использованном в приведенном выше фрагменте, указано, через какой сегментный регистр надлежит обращаться к ячейке go_addr, содержащей адрес перехода. Здесь допустима замена сегме'нта, если сегмент с ячейкой go_addr адресуется через другой сегментный регистр, например, ES или CS.
Во втором варианте подчеркивается, что переход осуществляется через ячейку размером в одно слово и, следовательно, является ближним. Ячейка go_addr могла быть объявлена с помощью директивы dd и содержать полный двухсловный адрес перехода, требуемый для реализации дальнего перехода. Однако сю можно воспользоваться и для ближнего перехода. Описатель word ptr перед именем ячейки с адресом перехода заставляет транслятор считать, что она имеет размер 1 слово (независимо от того, как она была объявлена), и что переход, следовательно, является ближним.
Наконец, возможен и самый простой, третий вариант, который совпадает по форме с прямым переходом, но, тем не менее, является косвенным, так как символическое обозначение go_addr является именем поля данных, а не программной меткой. В этом варианте предполагается, что сегмент, в котором находится ячейка go_addr, адресуется по умолчанию через регистр DS, хотя, как и во всех таких случаях, допустима замена сегмента. Тип перехода (ближний или дальний) определяется, исходя из размера ячейки go_addr. Однако этот вариант не всегда возможен. Для его правильной трансляции необходимо, чтобы транслятору к моменту обработки предложения
jmp go_addr
было уже известно, что собой представляет имя go_addr. Этого можно добиться двумя способами. Первый — расположить сегмент данных до сегмента команд, а не после, как в приведенном выше примере. Второй — заставить транслятор обрабатывать исходный текст программы не один раз, как он это делает по умолчанию, а несколько. Число проходов для транслятора TASM можно задать при его вызове с помощью ключа /т#, где # — требуемое число проходов. В нашем случае достаточно двух проходов.
В приведенных примерах адрес поля памяти с адресом точки перехода задавался непосредственно в коде команды косвенного перехода. Однако этот адрес можно задать и в одном из регистров общего назначения (ВХ, SI или DI). Для приведенного выше примера косвенного перехода в точку
so
Глава 2
Основы программирования
go, адрес которой находится в ячейке go_addr в сегменте данных, переход с использованием косвенной регистровой адресации может выглядеть следующим образом:
mov BX,offset go_addr jmp [BX] |
;B ВХ смещение поля с адресом перехода;Переход в точку go
Особенно большие возможности предоставляет методика косвенного перехода с использованием базово-индексной адресации через пары регистров, например, [BX][SI] или [BX][DI]. Этот способ удобен в тех случаях, когда имеется ряд альтернативных точек перехода, выбор которых зависит от некоторых условий. В этом случае в сегменте данных создается не одно поле с адресом, а таблица адресов переходов. В базовый регистр ВХ загружается адрес этой таблицы, а в один из индексных регистров — определенный тем или иным способом индекс в этой таблице. Переход осуществляется в точку, соответствующую заданному индексу. Структура программы, использующий такую методику, выглядит следующим образом:
code segment mov BX,offset go_tbl mov SI,4 jmp [BX][SIj gol: go2: go3: code ends data segment go_tbl label word gol_addrdw gol go2_addrdw go2 go3_addrdw go3 data ends |
;3агружаем в ВХ базовый адрес таблицы
вычисленное каким-то
;образом смещение в таблице
;Если индекс =4, переход в точку go3
;1-я точка перехода;2-я точка перехода;3-я точка перехода
;Таблица адресов переходов
;Адрес первой альтернативной
;точки перехода
;Адрес второй альтернативной
;точки перехода
;Адрсс третьей альтернативной
;точки перехода
Приведенный пример носит условный характер; в реальной программе индекс, помещаемый в регистр SI, должен вычисляться по результатам анализа некоторых условий.
Наконец, существует еще одна разновидность косвенного перехода, в котором не используется сегмент данных, а адрес перехода помещается непосредственно в один из регистров общего назначения. Часто такой
переход относят к категории прямых, а не косвенных, однако это вопрос не столько принципа, сколько терминологии.
Применительно к обозначениям последнего примера такой переход будет выглядеть, например, следующим образом:
mov BX,oflset gol jmp BX
Здесь, как и в предыдущих вариантах, имеется возможность вычисления адреса перехода, однако нельзя этот адрес индексировать.
Косвенный дальний (межсегментный) переход. Как и в случае ближнего перехода, переход осуществляется по адресу, который содержится в ячейке памяти, однако эта ячейка имеет размер 2 слова, и в ней содержится полный (сегмент плюс смещение) адрес точки перехода. Программа в этом случае должна включать по меньшей мере два сегмента команд. Структура программы с использованием косвенного дальнего перехода может выглядеть следующим образом:
codel segment
assume CS:code 1,DS:data
jmp DS:go_addr; Код FF 2E dddd
codel ends code2 segment assume CS:code2
go: code 2 data |
;Точка перехода в другом сегменте команд
ends segment
go_addr dd go Двухсловный адрес точки перехода
data |
ends
Точка перехода go находится в другом сегменте команд этой двухсег-ментной программы. В коде команды dddd обозначает относительный адрес слова go_addr в сегменте данных. Ячейка go_addr объявляется директивой dd (define double, определить двойное слово) и содержит двухсловный адрес точки перехода; в первом слове содержится смещение go в сегменте команд codel, во втором слове сегментный адрес code2. Оба компонента адреса перехода могут быть вычислены и помещены в ячейку go_addr по ходу выполнения программы.
Как и в случае ближнего косвенного перехода, ассемблер допускает различные формы описания дальнего косвенного перехода через ячейку сегмента данных:
Глава 2
Основы программирования
S3
jmp DS:go_addr;Возможна имена сегмента
jmp dword ptr go_addr;Если поле go_addr объявлено
;операторами dw
jmp go_addi Характеристики ячейки должны
;быть известны
Для дальнего косвенного перехода, как и для ближнего, допустима адресация через регистр общего назначения, если в него поместить адрес поля с адресом перехода:
mov BX,offset go_addr jmp [BX]
Возможно также использование базово-индексной адресации, если в сегменте данных имеется таблица с двухсловными адресами точек переходов.
2.7. Вызовы подпрограмм
Практически в любой программе, независимо от се содержания, встречаются участки, которые требуется выполнять (возможно, с небольшими изменениями) несколько раз по ходу программы. Такие повторяющиеся участки целесообразно выделить из общей программы, оформить в виде подпрограмм и обращаться к ним каждый раз, когда в основной программе возникает необходимость их выполнения.
Подпрограмма, в зависимости от выполняемых ею функций, может требовать передачи из вызывающей программы определенных данных (называемых аргументами, или параметрами), возвращать в вызывающую программу результаты вычислений или обходиться и без того, и без другого.
Подпрограмма может быть оформлена в виде процедуры, и тогда имя этой процедуры будет служить точкой входа в подпрограмму:
drawline proc |
; Подпрограмма -про цедура
ret endp |
;Тело подпрограммы
;Команда возврата в вызывающую программу
drawlme
С таким же успехом можно обойтись без процедуры, просто пометив первую строку программы некоторой меткой:
drawline:; Под программа, начинающаяся с метки
;Тело подпрограммы
ret;Команда возврата в вызывающую программу Продолжение основной программы или;другие подпрограммы
В любом случае вызов подпрограммы осущестшшется командой call. Подпрограмма должна завершаться командой ret, служащей для возврата управления в ту точку, откуда подпрограмма была вызвана.
Вопросы использования подпрограмм, передачи в них параметров и возвращения результата будут рассмотрены в следующей главе. Здесь мы остановимся только на таких принципиальных архитектурных вопросах,
как механизм выполнения и возможности команд call и ret. При этом надо иметь в виду, что синтаксические особенности и закономерности использования команд call и jmp во многом совпадают, и значительная часть пояснений к командам перехода справедлива и для команд вызова.
Команда вызова подпрограммы call может использоваться в 4 разновидностях. Вызов может быть:
прямым ближним (в пределах текущего сегмента команд);
прямым дальним (в другой сегмент команд);
косвенным ближним (в пределах текущего сегмента команд через ячейку с адресом перехода);
косвенным дальним (в другой сегмент команд через ячейку с адресом
перехода).
Рассмотрим последовательно перечисленные варианты.
code segment main proc call |
Прямой ближний вызов. Как и в случае прямого ближнего перехода, в команде прямого вызова в явной форме указывается адрес (смещение) точки входа в подпрограмму; в качестве этого адреса можно использовать как имя процедуры, так и имя метки, характеризующей точку входа в подпрограмму. В код команды, кроме кода операции E8h, входит смешение к вызываемой подпрограмме. В приведенном ниже примере подпрограмма оформлена в виде процедуры.
;Основная программа;Код Е8 dddd
sub
Подпрограмма;Код СЗ |
main endp.
sub proc near
ret
sub endp code ends
Про цедура-программа находится в том же сегменте команд, что и вызывающая программа. В коде команды dddd обозначает смещение в сегменте команд к точке входа в подпрограмму. При выполнении команды call процессор помещает адрес возврата (содержимое регистра IP) в стек выполняемой программы (рис. 2.16), после чего к текущему содержимому IP прибавляет dddd. В результате в IP оказывается адрес подпрограммы. Команда ret, которой заканчивается подпрограмма, выполняет обратную процедуру — извлекает из стека адрес возврата и заносит его в IP.
•\ Состояние стека после входа в подпрограмму
4 — Исходное состояние SP
Рис. 2.16. Участие стека в механизме вызова ближней подпрограммы.
Глава'2
Основы программирования
Участие стека в механизме вызова подпрограммы и возврата из нее является решающим. Поскольку в стеке хранится адрес возврата, подпрограмма, сама используя стек, например, для хранения промежуточных результатов, обязана к моменту выполнения команды ret вернуть стек в исходное состояние. Команда ret, естественно, никак не анализирует состояние или содержимое стека. Она просто снимает со стека верхнее слово, считая его адресом возврата, и загружает это слово в указатель команд IP. Если к моменту выполнения команды ret указатель стека окажется смещенным в ту или иную сторону, команда ret по-прежнему будет рассматривать верхнее слово стека, как адрес возврата, и передаст по нему управление, что неминуемо приведет к краху системы.
; Основная программа far ptr subr;Код 9А dddd ssss |
;Объявляем подпрограмму.;дальней;Код СВ — дальний возврат |
Прямой дальний вызов. Этот вызов позволяет обратиться к подпрограмме из другого сегмента. В код команды, кроме кода операции 9Ah, входит полный адрес (сегмент плюс смещение) вызываемой подпрограммы. Обычно в исходном тексте программы с помощью описателя far ptr указывается, что вызов является дальним, хотя, если транслятор настроен на трансляцию в два прохода, этот описатель не обязателен. Структура программного комплекса, содержащая дальний вызов подпрограммы, может выглядеть следующим образом:
codel assume | segment CS:codel |
main | proc call far |
main codel | endp ends |
code 2 assume | segment CS:code2 |
subr | proc far |
ret | |
subr code 2 | cndp ends |
Процедура-подпрограмма находится в другом сегменте команд той же программы. В коде команды dddd обозначает относительный адрес точки входа в подпрограмму в ее сегменте команд, a ssss — ее сегментный адрес. При выполнении команды call процессор помещает в стек сначшга сегментный адрес вызывающей программы, а затем относительный адрес возврата (рис. 2.17). Далее в сегментный регистр CS заносится ssss (у нас это значение code2), а в IP — dddd (у нас это значение subr). Поскольку процедура-подпрограмма атрибутом far объявлена датьней, команда ret имеет код, отличный от кода аналогичной команды ближней процедуры и выполняется по-другому: из стека извлекаются два верхних слова и переносятся в IP и CS, чем и осуществляется возврат в вызывающую программу, находящуюся в другом сегменте команд. В языке ассемблера существует и явное мнемоническое обозначение команды дальнего возврата — retf.
^— Состояние стека после входа в подпрограмму
^— Исходное состояние SP Рис. 2.17. Участие стека в механизме вызова дальней подпрограммы.
Косвенный ближний вызов. Адрес подпрограммы содержится либо в ячейке памяти, либо в регистре. Это позволяет, как и в случае косвенного ближнего перехода, модифицировать адрес вызова, а также осуществлять вызов не с помощью метки, а по известному абсолютному адресу. Структура программы с косвенным вызовом подпрограммы может выглядеть следующим образом:
code main |
segment proc |
;Основная программа