Рассылка закрыта
При закрытии подписчики были переданы в рассылку "RFpro.ru: Ассемблер? Это просто! Учимся программировать" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Уроки ассемблеру. Быстро и просто. Урок 10
Сегодня мы поговорим о сегментах. Возьмём для примера программу вывода строки из урока 2: .286 ;Разрешает ассемблирование непривилегированных инструкций ;процессора 80286 (реальный режим) и инструкций арифметического ;сопроцессора 80287. CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный ;сегмент в памяти программ по определённому адресу. ;Имя нашего сегмента будет CSEG. assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для ;вычисления действующего адреса для всех меток и переменных, опре- ;делённых для сегмента или группы сегментов с указанным именем. ;У нас их четыре, - CS, DS, ES, SS и они будут указывать на наш ;единственный сегмент (мы его назвали CSEG). org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот- ;ветствии с адресом, задаваемым "выражением". ;Сейчас этот счётчик равен 100h - используется для всех программ ;типа .com begin: ;Метка начала программы. ;Выводим фразу, используя функцию 9 прерывания 21h. mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. int 20h ;Выходим в DOS. helloworld db 'Hello, world!$' ;Определяем переменную helloworld, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". CSEG ends ;Указываем на завершение сегмента CSEG. end begin ;Конец программы. Обратим внимание, что в тексте программы хранятся не только коды программ, но и переменные, которые мы умышленно указываем в конце кода. Иначе может получиться так, что эти данные ассемблер примет за часть кода и... тогда случится непоправимое. Таким образом, получается, что в тексте одной программы хранить и код, и данные плохо и неудобно. Поэтому обычно программы, написанные на ассемблере, состоят из сегментов. Давайте попробуем вывести на экран DOS две фразы вида "Hello, world!" и "Hello, world! (2)". Как это сделать привычным нам способом вы, думаю, знаете: .286 ;Разрешает ассемблирование непривилегированных инструкций ;процессора 80286 (реальный режим) и инструкций арифметического ;сопроцессора 80287. CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный ;сегмент в памяти программ по определённому адресу. ;Имя нашего сегмента будет CSEG. assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для ;вычисления действующего адреса для всех меток и переменных, опре- ;делённых для сегмента или группы сегментов с указанным именем. ;У нас их четыре, - CS, DS, ES, SS и они будут указывать на наш ;единственный сегмент (мы его назвали CSEG). org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот- ;ветствии с адресом, задаваемым "выражением". ;Сейчас этот счётчик равен 100h - используется для всех программ ;типа .com begin: ;Метка начала программы. ;Выводим фразу, используя функцию 9 прерывания 21h. mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. ;Выводим фразу, используя функцию 9 прерывания 21h. mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. int 20h ;Выходим в DOS. helloworld1 db 'Hello, world!$' ;Определяем переменную helloworld1, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". helloworld2 db 'Hello, world! (2)$' ;Определяем переменную helloworld2, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". CSEG ends ;Указываем на завершение сегмента CSEG. end begin ;Конец программы. А теперь поместим данные об этих фразах в два разных сегмента. Внимание! До нынешнего момента мы создавали программы .com, работающие только с одним сегментом! Пора перейти к созданию *.exe файлов. Создаём нижеследующий файл test.asm в кодировке 866 и вводим нашу команду без атрибутов: ml test.asm: .286 ;Разрешает ассемблирование непривилегированных инструкций ;процессора 80286 (реальный режим) и инструкций арифметического ;сопроцессора 80287. MESSAGE1 segment USE16 ;Начало сегмента MESSAGE1 ;Сообщаем ассемблеру, что внутрисегментная адресация является ;16-ти разрядной - use16 helloworld1 db 'Hello, world!$' ;Определяем переменную helloworld1, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". MESSAGE1 ends ;Указываем на завершение сегмента MESSAGE1. MESSAGE2 segment USE16 ;Начало сегмента MESSAGE2 ;Сообщаем ассемблеру, что внутрисегментная адресация является ;16-ти разрядной - use16 helloworld2 db 'Hello, world! (2)$' ;Определяем переменную helloworld2, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". MESSAGE2 ends ;Указываем на завершение сегмента MESSAGE2. CSEG segment USE16 ;Начало сегмента CSEG - в котором идёт наш код программы. ;Сообщаем ассемблеру, что внутрисегментная адресация является ;16-ти разрядной - use16 assume cs:CSEG, ds:MESSAGE1 ;Для сегментирования адресов из сегмента CSEG выбирается регистр cs, ;а для сегментирования адресов из сегмента MESSAGE1 - ds. ;org 100h ;Внимание! Директива org 100h не используется!!! begin: ;Начало программы - точка входа. mov ax,MESSAGE1 ;Заносим адрес первого сегмента в ds черех ax. mov ds,ax ; mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. mov ax,MESSAGE2 ;Заносим адрес второго сегмента в ds черех ax. mov ds,ax ; mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. ;Выходим в DOS mov ah,4Ch ;Используем вместо int 20h int 21h CSEG ends ;Указываем на завершение сегмента CSEG. end begin ;Конец программы (точки входа). У нас должен быть создан файл test.exe, правда, с сообщением об ошибке вроде: "LINK : warning L4021: no stack segment". Сейчас это информационное сообщение (для небольших программ можно проигнорировать). Стек имеет смысл задавать, если это действительно надо, например, для больших и многосегментных программ. Но чтобы убрать сообщение об ошибке, нам достаточно после директивы .286 дописать следующую часть кода: dw 10 dup(?) SST ENDS Что действительно достойно внимания в этой программе. MESSAGE1 segment USE16 - Без "USE16" программа-ассемблер будет использовать USE32 (т.е. 32-х разрядные данные) и выдавать ошибку на строках вида mov dx,offset Message; assume cs:CSEG, ds:MESSAGE1 - Указываем masm, что в значение сегмента CSEG установлен сегментный регистр cs, а в значение сегмента MESSAGE1 установлен сегментный регистр ds; В сегменте MESSAGE1 хранится наша первая строка, мы и направляем его в ds; ;org 100h - Все COM программы начинаются с адреса 100h. Директива "org 100h" нужна для резервирования места для PSP (префикс программного сегмента). Когда COM-программа начинает работать, все сегментные регистры содержат адрес PSP - 256-байтового (т.е. 100h в шестнадцатеричном исчислении) блока, который резервируется операционной системой DOS непосредственно перед COM или EXE программой в памяти. Так как адресация начинается со шестнадцатеричного смещения 100 от начала PSP, то в программе после оператора SEGMENT кодируется директива ORG 100h. EXE файлы же используют дополнительно регистры (и стек), и необходимость непосредственного указания в адресации со шестнадцатеричного смещения 100 отпадает (но PSP остаётся!). mov ax,MESSAGE1 mov ds,ax - Заносим данные о MESSAGE1 в ds, но не напрямую, а через регистр ax (напрямую занести их нельзя). Направляем регистр ds на наш первый сегмент. Чуть дальше мы определим в dx смещение для фразы "Hello, world!". Теперь нам должны быть понятен текст вроде: "Данные о фразе 'Hello, world!' хранятся в DS:DX". Далее мы сменим сегмент и определим в dx новое смещение; mov ah,4Ch - Раньше для выхода в DOS мы использовали прерывание 20h. Для *.exe-программы мы будем использовать функцию 4Ch прерывания 21h. Усложним
немного пример, но для начала расскажем о моделях памяти и директиве
.model. Данная директива указывается в начале кода программы. Модели памяти
Автор таблицы: asmforfun А теперь давайте усложним предыдущий пример. Все данные у нас будут находиться в одном сегменте, а исполняемый код — сначала в одном, потом в другом. То есть в определённый момент мы "прыгнем" из сегмента в сегмент. Посмотрим, как это будет выглядеть: .286 ;Разрешает ассемблирование непривилегированных инструкций ;процессора 80286 (реальный режим) и инструкций арифметического ;сопроцессора 80287. .model small ;Указываем модель. ;Код занимает один сегмент, данные объединены в одну группу ;с именем DGROUP. ;Эту модель обычно используют для большинства программ на ассемблере SST segment stack "stack" ;Начало сегмента SST, определяющего стек. dw 10 dup(?) ;Резервируем область памяти. SST ENDS ;Указываем на завершение сегмента SST. DATAS segment USE16 ;Начало сегмента DATAS ;Сообщаем ассемблеру, что внутрисегментная адресация является ;16-ти разрядной - use16 helloworld1 db 'Hello, world!$' ;Определяем переменную helloworld1, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". helloworld2 db 'Hello, world! (2)$' ;Определяем переменную helloworld2, доступную побайтно, с фразой ;"Hello, world!". В одинарных кавычках, после знака "!" ставим ;знак "$". DATAS ends ;Указываем на завершение сегмента DATAS2. DATA2 segment USE16 newmetka: mov ax,DATAS mov ds,ax mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. ;Выходим в DOS mov ah,4Ch ;Используем вместо int 20h int 21h DATA2 ends CSEG segment USE16 ;Начало сегмента CSEG - в котором идёт наш код программы. ;Сообщаем ассемблеру, что внутрисегментная адресация является ;16-ти разрядной - use16 assume cs:CSEG, ds:DATAS, es:DATA2, ss:SST ; begin: ;Начало программы - точка входа. mov ax,DATAS mov ds,ax mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию). mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить. int 21h ;Выводим фразу. mov ax,DATA2 mov ds,ax JMP FAR PTR newmetka CSEG ends ;Указываем на завершение сегмента CSEG. end begin ;Конец программы (точки входа). Здесь мы видим новую инструкцию — JMP FAR PTR newmetka Она означает, что мы "прыгаем" (вспоминаем команду jmp) в сегмент, указанный в ds, на метку newmetka. Команда jmp имеет пять разновидностей: — переход прямой короткий (в пределах -128... + 127 байтов); — переход прямой ближний (в пределах текущего программного сегмента) ; — переход прямой дальний (в другой программный сегмент); — переход косвенный ближний; — переход косвенный дальний. Все разновидности переходов имеют одну и ту же мнемонику jmp, хотя и различающиеся коды операций. Во многих случаях транслятор может определить вид перехода по контексту, в тех же случаях, когда это невозможно, следует использовать атрибутные операторы: short - прямой короткий переход; near ptr - прямой ближний переход; far ptr - прямой дальний переход; word ptr - косвенный ближний переход; dword ptr - косвенный дальний переход. На сегодня это, пожалуй, всё. |
В избранное | ||