INT 3

Вызов прерывания 3 (#BP, точка останова)

8086

int 3

CD ib

INT imm8

Вызов прерывания imm8

8086

int 13

INTO

Вызов прерывания 4 (#OF, переполнение), если EFLAGS.OF=1

8086

into

Описание:

Команда INT 3 предназначена для генерации прерывания 3 и по описанию операции идентична команде INT n за исключением того, что номер прерывания здесь не присутствует непосредственно в коде операции, но неявно задается равным 3.

Данное прерывание предназначено для использования отладчиком, который размещает специальную однобайтную команду INT 3 (код CCh) вместо первого байта команд или вместо однобайтных команд.

Существует второй способ вызова данного прерывания с помощью двухбайтного кода INT 3 (код CD03h). Однако данный метод на практике не применяется, все ассемблеры x86 по умолчанию интерпретируют мнемонику INT 3 как однобайтную команду с кодом CCh (но это не исключает возможность ручного программирования двухбайтного кода). Помимо размера кода, отличается и процесс обработки одно- и двухбайтных команд INT 3 . Прерывание, сгенерированное однобайтной командой в режиме EV86 (CR4.VME = 1), не подвергается перенаправлению по карте перенаправления прерываний (как это описано для Режима 2 , Режима 3 , Режима 5) и всегда обрабатывается обработчиком защищенного режима через дескриптор в таблице IDT . Кроме того, в режиме V86 для данного прерывания не осуществляется проверок поля IOPL и, соответственно, не может быть сгенерирована ошибка общей защиты #GP когда EFLAGS.IOPL < 3, то есть однобайтная команда не является IOPL-чувствительной .

Операция:

Представленный здесь алгоритм описывает не только поведение процессора при выполнении команды INT 3 внешнего прерывания или генерации особой ситуации .

THEN GOTO REAL-ADDRESS-MODE ;

IF (EFLAGS.VM = 1 AND EFLAGS.IOPL < 3 AND

(CR4.VME = 0 OR CR4.VME = 1 AND IRB[n] = 1)

) (* IRB[n] - бит, соответствующий прерыванию n в карте перенаправления прерываний *)

#GP(0); (Программное прерывание INT n в режиме: (1) V86 при EFLAGS.IOPL < 3, (2) EV86 Режим 2 *)

ELSE (* Защищенный режим или режим V86/EV86 *)

IF (EFLAGS.VM = 1 AND CR4.VME = 1 AND

(INT n) AND IRB[n] = 0)

ELSE GOTO PROTECTED-MODE ; (* Аппаратные прерывания, особые ситуации; программные прерывания INT n в режиме: (1) Защищенный режим, (2) V86 при EFLAGS.IOPL = 3, (3) EV86 Режим 1 или Режим 4 *)

REAL-ADDRES-MODE:

IF ((Номер прерывания * 4) + 3 выходит за пределы сегмента при обращении к таблице векторов прерываний IVT) THEN #GP; FI;

IF (В стеке нет места для 6 байт) THEN #SS; FI;

EFLAGS.IF = 0; (* Сброс флага прерываний *)

EFLAGS.TF = 0; (* Сброс флага ловушки *)

EFLAGS.AC = 0; (* Сброс флага режима контроля выравнивания *)

CS = IVT[Номер прерывания * 4].selector;

EIP = IVT[Номер прерывания * 4].offset AND 0x0000FFFFh;

(* Продолжение работы в режиме реальной адресации... *)

EV86-MODE: (* CR0.PE = 1, EFLAGS.VM = 1, CR4.VME = 1, Режим EV86 - программное прерывание INT n, при IRB[n] = 0 - Режим 3 или Режим 5 *)

IF (В стеке задачи V86 нет места для 6 байт) THEN #SS(0); FI;

tempFLAGS = FLAGS;

tempFLAGS.NT = 0;

THEN EFLAGS.IF = 0; (* Сброс флага разрешения прерываний *)

tempFLAGS.IF = EFLAGS.VIF;

EFLAGS.VIF = 0; (* Сброс виртуального флага прерываний *)

EFLAGS.TF = 0; (* Сброс флага ловушки *)

Push(tempFLAGS);

(* В стек не заносятся коды ошибок *)

CS = IVT_V86[Номер прерывания * 4].selector; (* Таблица векторов прерываний IVT_V86 располагается в начале адресного пространства задачи V86 *)

EIP = IVT_V86[Номер прерывания * 4].offset AND 0x0000FFFFh; (* старшие 16-бит регистра EIP обнуляются *)

(* Продолжение работы в режиме EV86 ... *)

PROTECTED-MODE: (* CR0.PE = 1, Аппаратные прерывания, особые ситуации; программные прерывания INT n в режиме: (1) Защищенный режим, (2) V86 при EFLAGS.IOPL = 3, (3) EV86 Режим 1 или Режим 4 *)

IF ((Номер прерывания * 8) + 7 не попадает в пределы таблицы IDT) THEN #GP(номер прерывания * 8 + 2 + EXT); FI;

(* Здесь и далее в параметрах кода ошибки слагаемое +2 означает установку бита IDT кода ошибки, а слагаемое +EXT - означает установку бита EXT кода ошибки в соответствии с тем, было ли вызвавшее ошибку прерывание программным EXT = 0 или внешним EXT = 1 *)

Байт AR дескриптора должен задавать шлюз прерывания, шлюз ловушки или шлюз задачи, иначе #GP(номер прерывания * 8 + 2 + EXT);

IF (Программное прерывание или особая ситуация) (* Т.е. один из случаев INT n, INT 3, INT01, BOUND или INTO *)

IF (CPL > DPL шлюза)

#GP(номер прерывания * 8 + 2); (* CR0.PE = 1, DPL шлюза < CPL, программное прерывание *)

Шлюз должен присутствовать, иначе #NP(номер прерывания * 8 + 2 + EXT);

IF (Шлюз задачи)

THEN GOTO TASK-GATE ;

GOTO TRAP-OR-INT-GATE ; (* CR0.PE = 1, шлюз прерывания или ловушки *)

TRAP-OR-INT-GATE: (* Защищенный режим или режим V86/EV86, шлюз ловушки или прерывания *)

Проверка нового селектора CS, заданного в дескрипторе шлюза, и соответствующего ему дескриптора из LDT или GDT:

Селектор должен быть не нулевым, иначе #GP(EXT);

Индекс селектора должен попадать в пределы таблицы дескрипторов, иначе #GP(селектор + EXT);

Выбранный дескриптор должен быть дескриптором сегмента кода, иначе #GP(селектор + EXT);

Сегмент должен присутствовать (P = 1), иначе #NP(селектор + EXT);

IF (Кодовый сегмент несогласованный) AND (DPL сегмента кода < CPL)

IF EFLAGS.VM = 0

THEN GOTO INT-TO-INTER-PRIV ; (* CR0.PE = 1, EFLAGS.VM = 0, шлюз прерывания или ловушки, несогласованный кодовый сегмент, DPL сегмента кода < CPL *)

ELSE (* EFLAGS.VM = 1 *)

IF (DPL нового сегмента кода ≠ 0) THEN #GP(Селектор кодового сегмента + EXT); FI;

GOTO INT-FROM-V86-MODE ;(* CR0.PE = 1, EFLAGS.VM = 1, шлюз прерывания или ловушки, DPL сегмента кода = 0, CPL = 3 *)

ELSE (* CR0.PE = 1, шлюз прерывания или ловушки, согласованный кодовый сегмент или несогласованный кодовый сегмент с DPL = CPL *)

IF EFLAGS.VM = 1 THEN #GP(Селектор кодового сегмента + EXT); FI;

IF ((Кодовый сегмент согласованный) OR (DPL сегмента кода = CPL))

THEN GOTO INT-TO-INTRA-PRIV ; (* CR0.PE = 1, шлюз прерывания или ловушки, DPL сегмента кода ≤ CPL для согласованного сегмента, DPL сегмента кода = CPL для несогласованного сегмента *)

ELSE #GP(Селектор кодового сегмента + EXT); (* DPL > CPL для согласованного сегмента или DPL ≠ CPL для несогласованного сегмента *)

INT-TO-INTER-PRIV: (* Защищенный режим, шлюз прерывания или ловушки, несогласованный кодовый сегмент, DPL сегмента кода < CPL *)

IF (Текущий TSS 32-битный)

TSSstackAddress = (new code segment DPL * 8) + 4

IF ((TSSstackAddress + 5) > Предел TSS) (* (TSSstackAddress + 7) >

NewSS = [База TSS + TSSstackAddress + 4]; (* загружается 2 байта *)

(* загружается 4 байта *)

ELSE (* Текущий TSS 16-битный *)

TSSstackAddress = (new code segment DPL * 4) + 2

IF ((TSSstackAddress + 3) > Предел TSS) (* (TSSstackAddress + 4) > Предел TSS - для некоторых моделей процессоров *)

THEN #TS(Селектор текущего TSS + EXT);

NewESP = [База TSS + TSSstackAddress]; (* загружается 2 байта *)

NewSS = [База TSS + TSSstackAddress + 2]; (* загружается 2 байта *)

RPL селектора должен быть равен DPL нового кодового сегмента, иначе #TS(SS селектор + EXT);

DPL стекового сегмента должен быть равен DPL нового кодового сегмента, иначе #TS(SS селектор + EXT);

IF (32-битный шлюз)

THEN Новый стек должен иметь место для 20 байт (24 байт, если есть код ошибки), иначе #SS(EXT)

ELSE Новый стек должен иметь место для 10 байт (12 байт, если есть код ошибки), иначе #SS(EXT)

SS:ESP = TSS(NewSS:NewESP); (* Загрузить новые значения SS и eSP из TSS *)

IF (32-битный шлюз)

THEN

ELSE CS:IP = Gate(SELECTOR:OFFSET);

Загрузить дескриптор SS в скрытую часть регистра SS;

IF (32-битный шлюз)

Push(Длинный указатель на старый стек - SS:ESP);

Push(Длинный указатель на точку возврата - CS:EIP); (* 3 слова дополняются до 4 *)

Push(Код ошибки);

Push(Длинный указатель на старый стек - SS:SP); (* 2 слова *)

Push(Длинный указатель на точку возврата - CS:IP); (* 2 слова *)

Push(Код ошибки);

CPL = DPL нового кодового сегмента;

IF (Шлюз прерывания) THEN EFLAGS.IF = 0 FI; (* Сбросить флаг прерывания *)

EFLAGS.RF = 0;

(* Продолжение работы в защищенном режиме на уровне с большими привилегиями... *)

INT-FROM-V86-MODE: (* Режим V86/EV86, шлюз прерывания или ловушки, DPL = 0, CPL = 3 *)

(* Текущий TSS всегда 32-битный в режиме V86 *)

IF (Предел TSS < 9) (* Предел TSS < 11 - для некоторых моделей процессоров *)

THEN #TS(Селектор текущего TSS + EXT);

NewSS = [База TSS + 8]; (* загружается 2 байта *)

NewESP = [База TSS + 4]; (* загружается 4 байта *)

Проверка селектора нового стекового сегмента NewSS и соответствующего ему дескриптора из LDT или GDT:

Селектор должен быть не нулевым, иначе #TS(EXT);

Индекс селектора должен попадать в пределы таблицы дескрипторов, иначе #TS(SS селектор + EXT);

RPL селектора должен быть равен нулю, иначе #TS(SS селектор + EXT);

DPL стекового сегмента должен быть равен нулю, иначе #TS(SS селектор + EXT);

Дескриптор должен иметь формат дескриптора сегмента данных, разрешенного для записи (W = 1), иначе #TS(SS селектор + EXT);

Сегмент должен присутствовать (P = 1), иначе #SS(SS селектор + EXT);

IF (32-битный шлюз)

Новый стек должен иметь место для 36 байт (40 байт, если есть код ошибки), иначе #SS(EXT)

Новый указатель инструкции должен попадать в пределы нового кодового сегмента, иначе #GP(EXT); (* указатель инструкции определяется значением поля OFFSET из дескриптора шлюза *)

TempEflags = EFLAGS;

EFLAGS.VM = 0; (* Процессор выходит из режима V86 для обработки прерывания в защищенном режиме *)

IF (Шлюз прерывания)

THEN EFLAGS.IF = 0;

CPL = 0; (* Переключение на нулевой уровень привилегий *)

SS:ESP = TSS(NewSS:NewESP); (* Загрузить значения SS0 и ESP0 из TSS *)

Push(GS);

Push(FS); (* расширяется до двух слов *)

Push(DS); (* расширяется до двух слов *)

Push(ES); (* расширяется до двух слов *)

GS = 0; (* Сегментные регистры обнуляются. Недопустимо последующее использование нулевых селекторов в защищенном режиме *)

Push(TempSS); (* расширяется до двух слов *)

Push(TempEflags);

Push(CS); (* расширяется до двух слов *)

Push(Код ошибки); (* Если присутствует, 4 байта *)

CS:EIP = Gate(SELECTOR:OFFSET); (* Загрузить селектор:смещение из дескриптора 32-битного шлюза *)

ELSE (* 16-битный шлюз *)

Новый стек должен иметь место для 18 байт (20 байт, если есть код ошибки), иначе #SS(EXT)

(* сохранение в стеке 16-битных значений регистров происходит аналогично 32-битному шлюзу *)

(* Возврат из прерывания командой IRET обратно в режим V86 из 16-битного сегмента будет невозможен, так как флаг VM не сохранится в стеке и не восстановится из образа EFLAGS при возврате *)

(* Продолжение работы в защищенном режиме на нулевом уровне привилегий... *)

INT-TO-INTRA-PRIV: (* CR0.PE = 1, DPL = CPL или согласованный сегмент с DPL ≤ CPL *)

IF (32-битный шлюз)

THEN Новый стек должен иметь место для 12 байт (16 байт, если есть код ошибки), иначе #SS(EXT)

ELSE Новый стек должен иметь место для 6 байт (8 байт, если есть код ошибки), иначе #SS(EXT)

Новый указатель инструкции должен попадать в пределы нового кодового сегмента, иначе #GP(EXT);

IF (32-битный шлюз)

Push(Длинный указатель на точку возврата); (* 3 слова дополняются до 4 *)

CS:EIP = Gate(SELECTOR:OFFSET); (* Загрузить селектор:смещение из дескриптора 32-битного шлюза *)

Push(Код ошибки); (* Если присутствует, 4 байта *)

Push(Длинный указатель на точку возврата); (* 2 слова *)

CS:IP = Gate(SELECTOR:OFFSET); (* Загрузить селектор:смещение из дескриптора 16-битного шлюза *)

Push(Код ошибки); (* Если присутствует, 2 байта *)

Загрузить дескриптор CS в скрытую часть регистра CS;

IF (Шлюз прерывания) THEN EFLAGS.IF = 0; FI;

(* Продолжение работы в защищенном режиме без изменения уровня привилегий... *)

TASK-GATE: (* CR0.PE = 1, шлюз задачи *)

Проверка селектора TSS из дескриптора шлюза задачи:

Селектор должен задавать GDT (бит TI = 0), иначе #GP(TSS селектор + EXT);

Индекс селектора должен попадать в пределы таблицы GDT, иначе #GP(TSS селектор + EXT);

Проверка соответствующего выбранному селектору дескриптора TSS:

Дескриптор TSS должен иметь тип свободного TSS (TYPE.B = 0), иначе #GP(TSS селектор + EXT);

TSS должен присутствовать (P = 1), иначе #NP(TSS селектор + EXT);

SWITCH-TASKS(С вложением) в TSS; (* здесь "С вложением" означает тот факт, что при инициализации контекста новой задачи будет установлен флаг EFLAGS.NT = 1, а в поле LINK ее сегмента TSS будет скопирован селектор TSS прерываемой (старой) задачи - см. Адресация и многозадачность: Средства поддержки мультизадачности *)

IF (Прерывание представляет собой особую ситуацию с кодом ошибки)

В стеке должно быть место для кода ошибки, иначе #SS(EXT);

Push(код ошибки);

Указатель инструкции EIP должен попадать в пределы сегмента CS, иначе #GP(EXT);

(* Загрузка контекста новой задачи сопровождается дополнительными проверками как это описано в разделе Адресация и многозадачность: Средства поддержки мультизадачности *)

(* Продолжение работы в контексте новой задачи... *)

Особые ситуации защищенного режима:

INT 3 , но и при поступлении любого внешнего прерывания или генерации особой ситуации . Бит EXT в коде ошибки внешнего прерывания).

  • дескриптор вектору (индексу) прерывания, не находится в пределах таблицы дескрипторов прерываний (IDT) ;
  • дескриптор , соответствующий обрабатываемому вектору (индексу) прерывания, не является дескриптором шлюза ловушки , шлюза прерывания или шлюза задачи ;
  • имеет место программное прерывание или программная особая ситуация (то есть один из случаев: INT n , INT 3 , INT01, BOUND или INTO) и при этом текущий уровень привилегий (CPL) задачи больше уровня привилегий (DPL) дескриптора шлюза из таблицы IDT —
  • селектор сегмента в соответствующем обрабатываемому вектору (индексу) прерывания дескрипторе шлюза ловушки , шлюза прерывания или шлюза задачи является нулевым селектором ;
  • индекс селектора сегмента дескриптора шлюза ловушки или дескриптора шлюза прерывания не попадает в пределы соответствующей таблицы дескрипторов ;
  • индекс селектора TSS из соответствующего прерыванию дескриптора шлюза задачи не попадает в пределы глобальной таблицы дескрипторов (GDT);
  • дескриптор нового кодового сегмента не является дескриптором сегмента кода ;
  • (DPL) нового согласованного кодового сегмента больше текущего уровеня привилегий (CPL) задачи -
  • уровень привилегий дескриптора (DPL) нового несогласованного кодового сегмента не равен текущему уровеню привилегий (CPL) задачи -
  • селектор TSS из соответствующего прерыванию дескриптора шлюза задачи указывает на локальную таблицу дескрипторов (LDT);
  • дескриптор TSS новой задачи отмечен как занятый - TYPE.B ≠ 1.
  • адрес, по которому должно считываться новое значение для указателя стека (SS:eSP), выходит за пределы сегмента TSS ;
  • селектор нового сегмента стека является нулевым селектором ;
  • индекс селектора нового сегмента стека не попадает в пределы соответствующей таблицы дескрипторов ;
  • запрашиваемый уровень привилегий (RPL) селектора нового сегмента стека не равен (DPL) нового сегмента кода -
  • уровень привилегий дескриптора (DPL) нового сегмента стека не равен уровню привилегий дескриптора (DPL) нового сегмента кода -
  • новый стековый сегмент не является сегментом данных доступным для записи -
  • соответствующий прерыванию дескриптор шлюза ловушки , шлюза прерывания , шлюза задачи либо дескриптор TSS отмечен как неприсутствующий (бит P дескриптора сброшен);
  • новый сегмент кода не присутствует (бит P дескриптора сегмента сброшен).
  • при записи в стек (в том числе в новый стек, если имело место переключение стека) значений адреса возврата , указателя стека , флагов или кода ошибки просходит выход за допустимую границу стекового сегмента;
  • новый сегмент стека не присутствует (бит P дескриптора сегмента сброшен).

Особые ситуации режима реальной адресации:

Представленный здесь перечень особых ситуаций характеризует поведение процессора не только при выполнении команды INT 3 , но и при поступлении любого внешнего прерывания или генерации особой ситуации .

Особые ситуации режима V86:

Представленный здесь перечень особых ситуаций характеризует поведение процессора не только при выполнении команды INT 3 , но и при поступлении любого внешнего прерывания или генерации особой ситуации . Бит EXT в коде ошибки используется для индикации внешнего по отношению к прерванной программе события (

Ассемблеры MASM, TASM и WASM отличаются между собой. Однако создание простых программ для них практически не имеет отличий, за исключением самого ассемблирования и компоновки.

Итак, наша первая программа для MASM, TASM и WASM, которая выводит английскую букву «A» в текущей позиции курсора, то есть в левом верхнем углу экрана:

Model tiny .code ORG 100h start: MOV AH,2 MOV DL,41h INT 21h INT 20h END start Этот текст можно набрать в любом простом текстовом редакторе – например в БЛОКНОТЕ (NotePad) от WINDOWS (но не в Word и не в другом «навороченном»). Однако я рекомендую «продвинутый» текстовый редактор с подсветкой синтаксиса, например, PSPad (см. раздел ). Затем сохраняем этот файл с расширением.asm, например, в папке MYPROG. Назовем файл atest. Итак, мы получили: C:\MYPROG\atest.asm.

ПРИМЕЧАНИЕ
Обратите внимание, что в первой команде мы записали 2 вместо 02h. MASM, TASM и WASM, как и Emu8086, допускают такие «вольности». Хотя можно написать 02h – ошибки не будет.

Пояснения к программе :

.model tiny – 1-ая строка. Директива.model определяет модель памяти для конкретного типа файлов. В нашем случае это файл с расширением COM, поэтому выбираем модель tiny, в которой объединены сегменты кода, данных, и стека. Модель tiny предназначена для создания файлов типа СОМ.

.code – 2-ая строка. Эта директива начинает сегмент кода.

ORG 100h – 3-ая строка. Эта команда устанавливает значение программного счетчика в 100h, потому что при загрузке СОМ-файла в память, DOS выделяет под блок данных PSP первые 256 байт (десятичное число 256 равно шестнадцатеричному 100h). Код программы располагается только после этого блока. Все программы, которые компилируются в файлы типа СОМ, должны начинаться с этой директивы.

start: MOV AH, 02h – 4-я строка. Метка start располагается перед первой командой в программе и будет использоваться в директиве END, чтобы указать, с какой команды начинается программа. Инструкция MOV помещает значение второго операнда в первый операнд. То есть значение 02h помещается в регистр АН. Для чего это делается? 02h - это ДОСовская функция, которая выводит символ на экран. Мы пишем программу для DOS, поэтому используем команды этой операционной системы (ОС). А записываем мы эту функцию (а точнее ее номер) именно в регистр АН, потому что прерывание 21h использует именно этот регистр.

MOV DL, 41h – 5-я строка. Код символа «A» заносится в регистр DL. Код символа «A» по стандарту ASCII – это число 41h.

INT 21h – 6-я строка. Это и есть то самое прерывание 21h – команда, которая вызывает системную функцию DOS, заданную в регистре АН (в нашем примере это функция 02h). Команда INT 21h – основное средство взаимодействия программ с ОС.

INT 20h – 7-я строка. Это прерывание, которое сообщает операционной системе о выходе из программы, и о передаче управления консольному приложению. В том случае, если программа уже откомпилирована и запущена из ОС, команда INT 20h вернет нас в ОС (например, в DOS).

END start – 8-я строка. Директива END завершает программу, одновременно указывая, с какой метки должно начинаться ее выполнение.

Что такое Ассемблер

Ассемблер - низкоуровневый язык программирования. Для каждого процессора существует свой ассемблер. Программируя на ассемблере вы непосредственно работаете с аппаратурой компьютера. Исходный текст на языке ассемблера состоит из команд (мнемоник), которые после компиляции преобразуются в коды команд процессора.

Разработка программ на ассемблере - очень тяжёлая штука. Взамен потраченному времени вы получаете эффективную программу. Программы на ассемблере пишут, когда важен каждый такт процессора. На ассемблере вы даёте конкретные команды процессору и никакого лишнего мусора. Этим и достигается высокая скорость выполнения вашей программы.

Чтобы грамотно использовать ассемблер необходимо знать программную модель микропроцессорной системы. С точки зрения программиста микропроцессорная система состоит из:

  1. Микропроцессора
  2. Памяти
  3. Устройств ввода/вывода.

Программная модель хорошо описана в литературе .

Синтаксис Ассемблера

Общий формат строки программы на ассемблере

<Метка>: <Оператор> <Операнды> ; <Комментарий>

Поле метки. Метка может состоять из символов и знаков подчёркивания. Метки используются в операциях условного и безусловного перехода.

Поле оператора. В этом поле содержится мнемоника команды. Например мнемоника mov

Поле операндов. Операнды могут присутствовать только если присутствует Оператор (поле оператора). Операндов может не быть, а может быть несколько. Операндами могут быть данные, над которыми необходимо выполнить какие-то действия (переслать, сложить и т.д.).

Поле комментария. Комментарий нужен для словесного сопровождения программы. Всё, что стоит за символом ; считается комментарием.

Первая программа на языке Ассемблера

В этой статье будет использоваться ассемблер для i80x86 процессора и использоваться следующее программное обеспечение:

  • TASM - Borland Turbo Assembler - компилятор
  • TLINK - Borland Turbo Linker - редактор связей (компоновщик)

Если быть конкретным, то Tasm 2.0.

По традиции наша первая программа будет выводить строку "Hello world!" на экран.

Файл sample.asm

Model small ; Модель памяти.stack 100h ; Установка размера стека.data ; Начало сегмента данных программы HelloMsg DB "Hello World!",13,10,"$" .code ; Начало сегмента кода mov ax,@DATA ; Пересылаем адрес сегмента данных в регистр AX mov ds,ax ; Установка регистра DS на сегмент данных mov ah,09h ; DOS функция вывода строки на экран mov dx,offset HelloMsg ; Задаём смещение к началу строки int 21h ; Выводим строку mov ax,4C00h ; DOS функция выхода из программы int 21h ; Выход из программы end

Как вы могли заметить, что программа разделена на сегменты: сегмент данных, сегмент кода и есть ещё стековый сегмент.

Рассмотрим всё по порядку.

Директива.model small задаёт модель памяти. Модель small - это 1 сегмент для кода, 1 сегмент для данных и стека т.е. данные и стек находятся в одном сегменте. Бывают и другие модели памяти, например: tiny, medium, compact. В зависимости от выбранной вами модели памяти сегменты вашей программы могут перекрываться или могут иметь отдельные сегменты в памяти.

Директива.stack 100h задаёт размер стека. Стек необходим для сохранения некоторой информации с последующим её восстановлением. В частности стек используется при прерываниях. В этом случае содержимое регистра флагов FLAGS, регистра CS и регистра IP сохраняются в стеке. Далее идёт выполнение прерывающей программы, а потом идёт восстановление значений этих регистров.

  • Регистр флагов FLAGS содержит признаки, которые формируются после выполнения команды процессором.
  • Регистр CS (Code Segment) содержит адрес сегмента кода.
  • Регистр IP (Instruction Pointer) - указатель команд. Он содержит адрес команды, которая должная выполниться следующей (Адрес относительно сегмента кода CS).

Более подробное описание выходит за рамки простой статьи.

Директива.data определяет начало сегмента данных вашей программы. В сегменте данных определяются "переменные" т.е. идёт резервирование памяти под необходимые данные. После.data идёт строка
HelloMsg DB "Hello World!",13,10,"$"

Здесь HelloMsg - это символьное имя, которое соответствует началу строки "Hello World!" (без кавычек). То есть это адрес первого символа нашей строки относительно сегмента данных. Директива DB (Define Byte) определяет область памяти доступную по-байтно. 13,10 - коды символов Новая строка и Возврат каретки, а символ $ необходим для корректной работы DOS функции 09h. Итак, наша строка будет занимать в памяти 15 байт.

Директива.code определяет начало сегмента кода (CS - Code Segment) программы. Далее идут строки программы содержащие мнемоники команд.

Расскажу о команде mov.

mov <приёмник>, <источник>

Команда mov - команда пересылки. Она пересылает содержимое источника в приёмник. Пересылки могут быть регистр-регистр, регистр-память, память-регистр, а вот пересылки память-память нет т.е. всё проходит через регистры процессора.

Чтобы работать с данными необходимо настроить регистр сегмента данных. Настройка состоит в том, что мы записываем адрес сегмента данных @DATA в регистр DS (Data Segment). Непосредственно записать адрес в этот регистр нельзя - такова архитектура, поэтому мы используем регистр AX. В AX мы записываем адрес сегмента кода

а потом пересылаем содержимое регистра AX в регистр DS.

После этого регистр DS будет содержать адрес начала сегмента данных. По адресу DS:0000h будет содержаться символ H. Я предполагаю, что вы знаете о сегментах и смещениях.

Адрес состоит из двух составляющих <Сегмент>:<Смещение>, где Сегмент это 2 байта и смещение - 2 байта. Получается 4 байта для доступа к любой ячейке памяти.

mov ah,09h
mov dx,offset HelloMsg
int 21h

Тут мы в регистр AH записываем число 09h - номер функции 21-го прерывания, которая выводит строку на экран.

В следующей строке мы в регистр DX записываем адрес(смущение) к началу нашей строки.

Далее мы вызываем прерывание 21h - это прерывание функций DOS. Прерывание - когда выполняющаяся программа прерывается и начинает выполнятся прерывающая программа. По номеру прерывания определяется адрес подпрограммы DOS, которая выводит строку символов на экран.

У вас наверняка возникнет вопрос: А почему мы записываем номер функции 09h в регистр AH ? И почему смещение к строке записываем в регистр DX ?
Ответ простой: для каждой функции определены конкретные регистры, которые содержат входные данные для этой функции. Посмотреть какие регистры нужны конкретным функциям вы можете в help"е.

mov ax,4C00h
int 21h

mov ax,4C00h - пересылаем номер функции в регистр AX. Функция 4C00h - выход из программы.

int 21h - выполняем прерывание (собственно выходим)

end - конец программы.

После директивы end компилятор всё игнорирует, поэтому можете там писать всё, что угодно:)

Если вы дочитали до конца, то вы герой!

Майко Г.В. Ассемблер для IBM PC: - М.: "Бизнес-Информ", "Сирин" 1999 г. - 212 с.