Mov asm: Инструкция MOV

Содержание

Инструкция MOV







ТВ онлайн: более 100 каналов в HD-качестве

1) Большинство каналов бесплатные. 2) Можно смотреть как на сайте, так и в приложениях для iOS, Android и Smart TV. 3) Множество телесериалов и фильмов бесплатно или за 1 рубль. 4) Несколько платных подписок, в том числе 18+ и телеканал «Дождь». 5) А также записи телепередач, новости, ТВ-шоу и др.
Подробнее…




Пожалуй, инструкция MOV в ассемблере самая простая. Синтаксис этой команды такой:


MOV ПРИЁМНИК, ИСТОЧНИК


С помощью этой команды можно переместить значение из ИСТОЧНИКА в
ПРИЁМНИК. То есть по сути команда MOV копирует содержимое
ИСТОЧНИКА и помещает это содержимое в ПРИЁМНИК.


Никакие флаги при этом НЕ изменяются.


При использовании этой команды следует учитывать, что имеются некоторые
ограничения. А именно, инструкция MOV не может:

  • Записывать данные в регистры CS и IP.
  • Копировать данные из одного сегментного регистра в другой сегментный регистр (сначала нужно скопировать данные в регистр общего назначения).
  • Копировать непосредственное значение в сегментный регистр (сначала нужно скопировать данные в регистр общего назначения).


ИСТОЧНИКОМ может быть один из следующих:

  • Область памяти (MEM)
  • Регистр общего назначения (REG)
  • Непосредственное значение (например, число) (IMM)
  • Сегментный регистр (SREG)


ПРИЁМНИКОМ может быть один из следующих:

  • Область памяти (MEM)
  • Регистр общего назначения (REG)
  • Сегментный регистр (SREG)


С учётом ограничений, которые были описаны выше, комбинации ПРИЁМНИК-ИСТОЧНИК могут быть следующими:


REG,  MEM
SREG, MEM
MEM,  REG
REG,  REG
SREG, REG
MEM,  IMM
REG,  IMM
MEM,  SREG
REG,  SREG


Пример использования инструкции MOV:


MOV AX, 0B800h    ; установить AX = B800h (память VGA).
MOV DS, AX        ; копировать значение из AX в DS.
MOV CL, 'A'       ; CL = 41h (ASCII-код).
MOV CH, 01001110b ; CH = атрибуты цвета (желтый текст на красном фоне).
MOV BX, 72eh      ; BX = позиция на экране = 2*(x + y*80).
MOV [BX], CX      ; [0B800h:015Eh] = CX.


ПРИМЕЧАНИЕ

Этот пример не будет работать в Windows 2000 и выше, так как эти операционные системы запрещают программам напрямую обращаться к “железу”, а в этом примере мы пытаемся записать данные непосредственно в видеопамять.


Ну и напоследок скажу, почему эта инструкция называется MOV. Это сокращение
от английского слова MOVE, которое можно перевести как “переместить, перенести, передвинуть”. И, как теперь вам уже понятно, эта команда соответствует своему названию — она перемещает значение из одного регистра в другой. Хотя с точки зрения русского языка это будет не совсем правильно, потому что перемещения не происходит — значение из ИСТОЧНИКА никуда не исчезает (не перемещается), по сути оно копируется и вставляется в ПРИЁМНИК.





Первые шаги в программирование

Главный вопрос начинающего программиста – с чего начать? Вроде бы есть желание, но иногда «не знаешь, как начать думать, чтобы до такого додуматься».
У человека, который никогда не имел дело с информационными технологиями, даже простые вопросы могут вызвать большие трудности и отнять много времени на решение.
Подробнее…


Assembler: 12. Команды расширения — IndigoBits

В программах довольно часто нужно переслать меньшее по длине значение в большую по длине переменную или регистр. В качестве примера предположим, что нам нужно загрузить 16-разрядное беззнаковое значение, хранящееся в переменной count, в 32-разрядный регистр ЕСХ. Самое простое решение этой задачи заключается в том, что вначале нужно обнулить регистр ЕСХ, а затем загрузить 16-разрядное значение переменной count в регистр СХ:

mov ecx, 0
mov cx, unsignedVal ; ECX = FFFFFFF0h (-16)

А что делать, если нужно загрузить в регистр ЕСХ отрицательное значение, например, -16? для получения правильного результата нам нужно было не обнулять регистр ЕСХ, а загрузить в него значение FFFFFFFFh, и только затем загрузить в регистр СХ переменную signedVal. Код будет выглядеть так:

mov ecx, 0FFFFFFFFh
mov cx, signedVal ; ECX = FFFFFFF0h (-16)

Тобишь, для решения задачи, вначале нужно проанализировать знак числа и в зависимости от результата загрузить в регистр либо 0, либо -1.

Для выполнения расширения можно использовать готовые команды:

Команда CBW (Convert byte to word)
Выполняет преобразование значения, находящегося в регистре al, до размера слова, и помещает его в ах, при этом свободные старшие биты ах заполняются знаковым битом al (al -> ax).

Результат выполнения для отрицательного и положительного числа
Mov al, -1; al =  10000001b
Cbw; ax = 11111111 10000001b 

Mov al, 1; al =  00000001b
Cbw; ax = 00000000 00000001b 

Команда CWD (Convert word to double)
Выполняет преобразование значения, находящегося в регистре ax, до размера двойного слова, и помещает его в пару регистров dx:ах, при этом свободные биты dx заполняются знаковым битом aх. (ax -> dx:ax)

Mov ax, -1; ax =  10000000 00000001b
Cwd; dx 11111111 11111111   ax =  10000000 00000001b

Mov ax, 1; ax =  00000000 00000001b
Cwd; dx = 00000000 00000000 ax =  0000000 00000001b

Команда CWDE (Convert word to double extended)
Выполняет преобразование значения, находящегося в регистре ax, до размера двойного слова, и помещает его в регистр eах, при этом свободные старшие биты eax заполняются знаковым битом aх. (ax -> eax)

Mov ax, -1; ax =  10000000 00000001b
Cwde; eax = 11111111 11111111 10000000 00000001b

Mov ax, 1; ax =  00000000 00000001b
Cwde; eax = 00000000 00000000 00000000 00000001b

Команда CDQ (Convert double to quadruple)
Выполняет преобразование значения, находящегося в регистре eax, до размера учетверенного слова, и помещает его в пару регистров edx:eах, при этом свободные биты edx заполняются знaковым битом eax. (eax -> edx:eax)

Mov eax, -1; eax =  10000000 00000000 00000000 00000001b
Cdq; 
;edx =  11111111 11111111 11111111 11111111b
 ;eax =  10000000 00000000 00000000 00000001b

Mov eax, 1; eax =  00000000 00000000 00000000 00000001b
Cdq; 
;edx =  00000000 00000000 00000000 00000000b
 ;eax =  00000000 00000000 00000000 00000001b

Команда MOVZX
Команда MOVZX копирует содержимое исходного операнда в больший по размеру регистр получателя данных. При этом оставшиеся неопределенными биты регистра-получателя (как правило, старшие 16 или 24 бита) сбрасываются в ноль. Эта команда используется только при работе с беззнаковыми целыми числами.

Команда MOVSX
Команда MOVSX (Move With Sign-Extend, или Переместить и дополнить знаком) копирует содержимое исходного операнда в больший по размеру регистр получателя данных, также как и команда MOVZX. При этом оставшиеся неопределенными биты регистра- получателя (как правило, старшие 16 или 24 бита) заполняются значением знакового бита исходного операнда. Эта команда используется только при работе со знаковыми целыми числами.

Команды MOVSX и MOVZX применимы к следующим операндам:
MOVSX/MOVZX 16-разрядный регистр (16r), 8-разрядный регистр или поле памяти(8r/8m)
MOVSX/MOVZX 32-разрядный регистр (32r), 8-разрядный регистр или поле памяти(8r/8m)
MOVSX/MOVZX 32-разрядный регистр (32r), 16-разрядный регистр или поле памяти(16r/16m)

Ассемблер. Базовый синтаксис | Уроки Ассемблера

  Обновл. 7 Дек 2020  | 

Программы на ассемблере могут быть разделены на три секции:

   секция data;

   секция bss;

   секция text.

Секции ассемблера

Секция data используется для объявления инициализированных данных или констант. Данные в этой секции НЕ могут быть изменены во время выполнения программы. Вы можете хранить константные значения и названия файлов в этой секции. Синтаксис объявления:

Секция bss используется для объявления переменных. Синтаксис объявления:

Секция text используется для хранения кода программы. Данная секция должна начинаться с объявления global_start, которое сообщает ядру, откуда нужно начинать выполнение программы. Синтаксис объявления:

section.text
global _start
_start:

section.text

   global _start

_start:

Комментарии

Комментарии в ассемблере должны начинаться с точки с запятой (;). Они могут содержать любой печатный символ, включая пробел. Комментарий может находиться как на отдельной строке:

; эта программа выводит сообщение на экран

; эта программа выводит сообщение на экран

Так и на строке со стейтментом:

add eax, ebx ; добавляет ebx к eax

add eax, ebx     ; добавляет ebx к eax

Стейтменты

В ассемблере есть три вида стейтментов:

   Выполняемые инструкции (или просто «инструкции») — сообщают процессору, что нужно делать. Каждая инструкция хранит в себе код операции (или «опкод») и генерирует одну инструкцию на машинном языке.

   Директивы ассемблера — сообщают программе об аспектах компиляции. Они не генерируют инструкции на машинном языке.

   Макросы — являются простым механизмом вставки кода.

В ассемблере на одну строку приходится один стейтмент, который должен соответствовать следующему формату:

[метка] mnemonic [операнды] [; комментарий]

[метка]   mnemonic   [операнды]   [; комментарий]

Базовая инструкция состоит из названия инструкции (mnemonic) и операндов (они же «параметры»). Вот примеры типичных стейтментов ассемблера:

INC COUNT ; выполняем инкремент переменной памяти COUNT

MOV TOTAL, 48 ; перемещаем значение 48 в переменную памяти TOTAL

ADD AH, BH ; добавляем содержимое регистра BH к регистру AH

AND MASK1, 128 ; выполняем операцию AND с переменной MASK1 и 128

ADD MARKS, 10 ; добавляем 10 к переменной MARKS
MOV AL, 10 ; перемещаем значение 10 в регистр AL

INC COUNT        ; выполняем инкремент переменной памяти COUNT

 

MOV TOTAL, 48    ; перемещаем значение 48 в переменную памяти TOTAL

  

ADD AH, BH       ; добавляем содержимое регистра BH к регистру AH

  

AND MASK1, 128   ; выполняем операцию AND с переменной MASK1 и 128

  

ADD MARKS, 10    ; добавляем 10 к переменной MARKS

MOV AL, 10       ; перемещаем значение 10 в регистр AL

Первая программа

Следующая программа на языке ассемблера выведет строку Hello, world! на экран:

section .text
global _start ; необходимо для линкера (ld)

_start: ; сообщает линкеру стартовую точку
mov edx,len ; длина строки
mov ecx,msg ; строка
mov ebx,1 ; дескриптор файла (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db ‘Hello, world!’, 0xa ; содержимое строки для вывода
len equ $ — msg ; длина строки

section .text

   global _start    ; необходимо для линкера (ld)

_start:             ; сообщает линкеру стартовую точку

   mov edx,len     ; длина строки

   mov ecx,msg     ; строка

   mov ebx,1       ; дескриптор файла (stdout)

   mov eax,4       ; номер системного вызова (sys_write)

   int 0x80        ; вызов ядра

   mov eax,1       ; номер системного вызова (sys_exit)

   int 0x80        ; вызов ядра

 

section .data

msg db ‘Hello, world!’, 0xa  ; содержимое строки для вывода

len equ $ — msg              ; длина строки

Результат выполнения программы:

Hello, world!

Сборка программ

Убедитесь, что у вас установлен NASM. Запишите вашу программу в текстовом редакторе и сохраните её как hello.asm. Затем:

   откройте терминал;

   убедитесь, что вы находитесь в той же директории, в которой вы сохранили hello.asm;

   чтобы собрать программу, введите команду nasm -f elf hello.asm;

   если не было ошибок, то создастся объектный файл вашей программы под названием hello.o;

   чтобы ваш объектный файл прошел линкинг и создался исполняемый файл под названием hello, введите команду ld -m elf_i386 -s -o hello hello.o;

   запустите программу командой ./hello.

Если всё прошло успешно, то вам выведется Hello, world!.

Если у вас нет возможности скомпилировать программу, например, у вас нет Linux и вы пока не хотите на него переходить, то можете использовать одну из следующих онлайн-IDE:

   TutorialsPoint

   JDoodle

Примечание: Запоминать две вышеприведенные команды для сборки программы на ассемблере для некоторых может быть несколько затруднительно, поэтому вы можете написать скрипт для сборки программ на ассемблере. Для этого создайте файл под названием Makefile со следующим содержимым:

all:
nasm –f elf $(source)
ld –m elf_i386 –s –o $(source) $(source).o
rm $(source).o

all:

    nasm –f elf $(source)

    ld –m elf_i386 –s –o $(source) $(source).o

    rm $(source).o

Для сборки hello.asm выполните следующие действия:

   откройте терминал;

   убедитесь, что вы находитесь в той же директории, в которой вы сохранили hello.asm и Makefile;

   введите команду make source=hello.

Оценить статью:

Загрузка…

Поделиться в социальных сетях:

Доступ к данным C или C++ в блоках __asm



  • Чтение занимает 2 мин

В этой статье

Блок, относящийся только к системам Microsoft

Большое удобство встроенного кода на языке ассемблера — это возможность ссылаться на переменные C или C++ по имени. __asm Блок может ссылаться на любые символы, включая имена переменных, которые находятся в области видимости блока. Например, если переменная C var находится в области видимости, инструкция

__asm mov eax, var

сохраняет значение переменной var в регистре EAX.

Если член класса, структуры или объединения имеет уникальное имя, __asm блок может ссылаться на него, используя только имя элемента, не указывая переменную или typedef имя перед оператором period (.). Однако если имя элемента не является уникальным, необходимо поместить переменную или typedef имя непосредственно перед оператором period. Например, в типах структур в следующем примере в качестве имен членов используется общее имя same_name.

Если переменные объявлены с типами

struct first_type hal;
struct second_type oat;

во всех ссылках на член same_name необходимо указывать имя переменной, поскольку имя same_name не является уникальным. Однако имя члена weasel уникально, поэтому на него можно ссылаться, используя только имя члена:

// InlineAssembler_Accessing_C_asm_Blocks.cpp
// processor: x86
#include <stdio.h>
struct first_type
{
   char *weasel;
   int same_name;
};

struct second_type
{
   int wonton;
   long same_name;
};

int main()
{
   struct first_type hal;
   struct second_type oat;

   __asm
   {
      lea ebx, hal
      mov ecx, [ebx]hal.same_name ; Must use 'hal'
      mov esi, [ebx].weasel       ; Can omit 'hal'
   }
   return 0;
}

Обратите внимание, что возможность не указывать имя переменной — это всего лишь вопрос удобства при написании кода. Создаваемые инструкции ассемблера не зависят от наличия или отсутствия имени переменной.

В C++ доступ к данным-членам возможен без учета ограничений доступа. Однако вызов функций-членов невозможен.

Завершение блока, относящегося только к системам Майкрософт

См. также

Использование C или C++ в блоках __asm



i8086.asm · GitHub

DOSSEG
;.86
.MODEL SMALL
.STACK 200h
;=========================== ТЕСТЫ =========================================
.CODE
; Каждый тест в основной программе загружается в область памяти модели,
; представляющей кусок памяти и объекты I8086
; Проверка JNE и SCASB (при DF = 0)
P_TEST0 PROC NEAR
JNE JNE_TEST1 ; нет прыжка, т.к ZF = 1
; в фигурных скобках установленные флаги
SCASB ; 55h == 44h { PF }
SCASB ; 55h == 55h { ZF, PF }
SCASB ; 55h == 66h { CF, AF, SF }
SCASB ; 55h == 00h { PF }
SCASB ; 55h == 55h { ZF, PF }
SCASB ; 55h == FFh { AF, PF, CF }
JNE_TEST1:
JNE JNE_TEST2 ; прыжок есть, т.к. ZF = 0
SCASB ; пропуск
SCASB ; пропуск
SCASB ; пропуск
JNE_TEST2:
HLT
ORG P_TEST0+32
; Три сегмента данных
DW 5544H,0066H,0FF55H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0000H,0001H,0A0BH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H,3040H
DW 55H,0,0,4,0,0,2,0 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,3 ; ES, CS, SS, DS
DB 0,0,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST0 ENDP
ORG P_TEST0+080H ; Огругление адреса теста, чтобы было легче
; сверять листинг с областью памяти
; Проверка SCASB (при DF = 1)
P_TEST1 PROC NEAR
SCASB ; 55h == FFh {DF, AF, PF, CF }
SCASB ; 55h == 55h {DF, ZF, PF }
SCASB ; 55h == 00h {DF, PF }
SCASB ; 55h == 66h {DF, CF, AF, SF }
SCASB ; 55h == 55h {DF, ZF, PF }
SCASB ; 55h == 44h {DF, PF }
HLT
ORG P_TEST1+32
; Три сегмента данных
DW 5544H,0066H,0FF55H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0000H,0001H,0A0BH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H,3040H
DW 55H,0,0,4,0,0,2,5 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,3 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST1 ENDP
ORG P_TEST1+080h
; тест PUSH
P_TEST2 PROC
PUSH ES
PUSH DS
PUSH AX
PUSH BP
; BX = 4
; DI = 2
; DS = 3 !!!!
; ES = 2 !!!!
PUSH [BX] ; DS:4
PUSH [BX + DI] ; DS:6
PUSH [BX + DI + 4] ; DS:10
PUSH ES:[BX + DI — 2] ; ES:4
HLT
ORG P_TEST2+32
DW 0102H,0304H,0506H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H
DW 0000H,0000H,0000H,0000H,0000H,0000H,0000H,0000H
DW 11H,22h,33h,4h,10h,66h,77h,2 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,3 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST2 ENDP
ORG P_TEST2+080h
; проверкa SHL
P_TEST3 PROC
SHL AX, 1 ; 55AAh << 1 = AB54h
SHL SI, CL ; 2010h << 3 = 0080h
SHL AH, 1 ; ABh << 1 = 56h
SHL DL, CL ; 03h << 3 = 18h
; CL = 3
; BX = 4
; DI = 8
; DS = 4 !!!!
; ES = 2 !!!!
SHL WORD PTR [BX], 1 ; DS:4 0809h << 1 = 1012h
SHL WORD PTR [BX + DI], CL ; DS:0C 1020h << 3 = 8100h
SHL BYTE PTR [BX + DI + 2], 1 ; DS:0E 40h << 1 = 80h
SHL BYTE PTR ES:[BX + DI — 2], CL ; ES:0A 0Ch << 1 = 60h
HLT
ORG P_TEST3+32
DW 0102H,0304H,0506H,0708H,055AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0607H,0855H,0ABBH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0ACBH,0C0DH,0E0FH,1020H,3040H
DW 55AAH,3,0203H,4,5,6,2010H,8 ; ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,3,4 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST3 ENDP
ORG P_TEST3+080h
P_TEST4 PROC
SUB AL, 00AAH
SUB AX, 22AAH
SUB DL, AL
SUB DX, AX
SUB AX, DL
HLT
ORG P_TEST4+32
DW 0102H,0304H,0506H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H,3040H
DW 55AAH,3,0,4,0,2,2,1 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,3 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST4 ENDP
ORG P_TEST4+080h
P_TEST5 PROC
HLT
ORG P_TEST5+32
DW 0102H,0304H,0506H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H,3040H
DW 1000010000100101b,3,0,4,0,2,2,1 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,3 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,0 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST5 ENDP
ORG P_TEST5+080h
P_TEST6 PROC
HLT
ORG P_TEST6+32
DW 017FH,0304H,0506H,0708H,090AH,0B0CH,0D0EH,0F0FH
DW 0203H,0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H
DW 0405H,0607H,0809H,0A0BH,0C0DH,0E0FH,1020H,3040H
DW 255,3,0,2,0,2,2,1 ; _AX _CX _DX _BX _SP _BP _SI _DI
DW 2,0,4,2 ; ES, CS, SS, DS
DB 0,1,0,1,0,1,1 ; OF,DF,SF,ZF,AF,PF,CF
P_TEST6 ENDP
.DATA
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; Модели объектов I8086 !
; Порядок объявление задан жестко. Менять нельзя!!! !
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
; MEM — массив памяти 80 байт
MEM LABEL BYTE
SEG_CS DB 32 DUP (0)
SEG_DS DB 16 DUP (0)
SEG_ES DB 16 DUP (0)
SEG_SS DB 16 DUP (0)
SZ_MEM = $ — MEM
; Регистры. Объявление должно непосредстванно примыкать к MEM
_AX LABEL WORD ; <-
_AL DB 01 ; \
_AH DB 02 ; | Регистры
_CX LABEL WORD ; | общего
_CL DB 00 ; | назначения
_CH DB 00 ; | имеют по три метки:
_DX LABEL WORD ; | — младший байт
_DL DB 05 ; | — старший байт
_DH DB 06 ; | — метка слова, совпадающая
_BX LABEL WORD ; | с меткой младшего байта
_BL DB 07 ; /
_BH DB 08 ; <-
_SP DW 16 ; <-
_BP DW 18 ; \ Адресные регистры
_SI DW 4 ; /
_DI DW 6 ; <-
_ES DW 3 ; <-
_CS DW 0 ; \ Сегментные регистры
_SS DW 4 ; /
_DS DW 2 ; <-
_OF DB 0 ; <-
_DF DB 0 ; \
_SF DB 0 ; |
_ZF DB 0 ; | Флаги
_AF DB 0 ; |
_PF DB 0 ; /
_CF DB 0 ; <-
EVEN
SZ_OBJ86 = $-MEM ; размер области объектов для копирования тестов
_IP DW 0 ; Счетчик команд. При эмуляции всегда начинается с 0.
; Части команды
_COP DB ? ; Код операции
_PBYTE DB ? ; Постбайт
P_SEGREG DW 0 ; УКАЗАТЕЛЬ СЕГМЕНТНОГО РЕГИСТРА ДЛЯ ЗАМЕНЫ
FL_PREF DW 0 ; Признак того, что был префикс замены
_VALUE DW 0 ; Непосредственный операнд длиной в байт
SW DB 0 ; Если 1, то непосредственный Byte расширяется и действует
; на слово
; ============== Данные для поддержки процесса моделирования ==========
; Таблица адресов тестовых фрагментов
AP_TEST DW P_TEST0,P_TEST1,P_TEST2,P_TEST3,P_TEST4
DW P_TEST5,P_TEST6,P_TEST1,P_TEST1,P_TEST1
NUMTEST DW 0 ;
_ZERO DW 0 ; Заглушка для методов адресации
A_MODRM DW 0 ; Указатель модели объекта, адресуемого MOD_RM
A_REG DW 0 ; Указатель модели объекта, адресуемого REG
;Константы для таблицы методов адресации
@NODISP EQU 0
@DISP8 EQU 1
@DISP16 EQU 2
@WORD EQU 1 ; маска для выделения признака обработки слова в КОП
;Таблица методов адресации
; Смещен. БазРег ИндРег СегмРег
T_ADR DW @NODISP, _BX, _SI, _DS ; <==
DW @NODISP, _BX, _DI, _DS ; \
DW @NODISP, _BP, _SI, _SS ; | Mod == 00
DW @NODISP, _BP, _DI, _SS ; |
DW @NODISP, _SI, _ZERO, _DS ; |
DW @NODISP, _DI, _ZERO, _DS ; |
DW @DISP16, _ZERO,_ZERO, _DS ; /
DW @NODISP, _BX, _ZERO, _DS ; <==
DW @DISP8, _BX, _SI, _DS ; <==
DW @DISP8, _BX, _DI, _DS ; \
DW @DISP8, _BP, _SI, _SS ; | Mod == 01
DW @DISP8, _BP, _DI, _SS ; |
DW @DISP8, _SI, _ZERO, _DS ; |
DW @DISP8, _DI, _ZERO, _DS ; |
DW @DISP8, _BP, _ZERO, _SS ; /
DW @DISP8, _BX, _ZERO, _DS ; <==
DW @DISP16, _BX, _SI, _DS ; <==
DW @DISP16, _BX, _DI, _DS ; \
DW @DISP16, _BP, _SI, _SS ; | Mod == 10
DW @DISP16, _BP, _DI, _SS ; |
DW @DISP16, _SI, _ZERO, _DS ; |
DW @DISP16, _DI, _ZERO, _DS ; |
DW @DISP16, _BP, _ZERO, _SS ; /
DW @DISP16, _BX, _ZERO, _DS ; <==
START_MSG DB 13,10,’Enter — выполнить команду; ESC — выход; 0..9 — номер теста’,0
TERM_MSG DB ‘До свидания.’,0
; Коды операции эмулируемых команд
;——-JNE——-
_JNE = 075h ; JNE
;——-PUSH——
_PUSH_REG0 = 50h ; PUSH r16
_PUSH_REG1 = 51h ; PUSH r16
_PUSH_REG2 = 52h ; PUSH r16
_PUSH_REG3 = 53h ; PUSH r16
_PUSH_REG4 = 54h ; PUSH r16
_PUSH_REG5 = 55h ; PUSH r16
_PUSH_REG6 = 56h ; PUSH r16
_PUSH_REG7 = 57h ; PUSH r16
_PUSH_RS0 = 0Eh
_PUSH_RS1 = 16h
_PUSH_RS2 = 1Eh
_PUSH_RS3 = 06h
_PUSH_RGM = 0FFh; r/m16
;——-SCASB——
_SCASB = 0AEh ; SCASB
;——-SHL———
_SHL_RM8_1 = 0D0h
_SHL_RM16_1 = 0D1h
_SHL_RM8_CL = 0D2h
_SHL_RM16_CL = 0D3h
;——-SUB———
_SUB_AL_IM8 = 2CH
_SUB_AX_IM16 = 2DH
_SUB_RGM_IM = 80H
_SUB_RGM16_IM16 = 81H
_SUB_RGM16_IM8 = 83H
_SUB_RGM_REG = 28H
_SUB_RGM16_REG16 = 29H
_SUB_REG_RGM = 2AH
_SUB_REG16_RGM16 = 2BH
;——-PREF——-
_PREF_ES = 26H ; ES:
_PREF_CS = 2EH ; CS:
_PREF_SS = 36H ; SS:
_PREF_DS = 3EH ; DS:
;Таблицы для ветвления по коду операции
T_COP DB _JNE
DB _PUSH_REG0, _PUSH_REG1, _PUSH_REG2, _PUSH_REG3, _PUSH_REG4, _PUSH_REG5, _PUSH_REG6, _PUSH_REG7
DB _PUSH_RS0, _PUSH_RS1, _PUSH_RS2, _PUSH_RS3
DB _PUSH_RGM
DB _SCASB
DB _SHL_RM8_1, _SHL_RM16_1 , _SHL_RM8_CL , _SHL_RM16_CL
DB _SUB_AL_IM8, _SUB_AX_IM16, _SUB_RGM_IM, _SUB_RGM16_IM16, _SUB_RGM16_IM8, _SUB_RGM_REG, _SUB_RGM16_REG16, _SUB_REG_RGM, _SUB_REG16_RGM16
DB _PREF_ES, _PREF_CS, _PREF_SS, _PREF_DS
SZ_TCOP = $ — T_COP
T_LBL DW @JNE
DW @PUSH_REG, @PUSH_REG, @PUSH_REG, @PUSH_REG, @PUSH_REG, @PUSH_REG, @PUSH_REG, @PUSH_REG
DW @PUSH_RS, @PUSH_RS, @PUSH_RS, @PUSH_RS
DW @PUSH_RGM
DW @SCASB
DW @SHL_RM8_1, @SHL_RM16_1 , @SHL_RM8_CL , @SHL_RM16_CL
DB @SUB_AL_IM8, @SUB_AX_IM16, @SUB_RGM_IM, @SUB_RGM16_IM16, @SUB_RGM16_IM8, @SUB_RGM_REG, @SUB_RGM16_REG16, @SUB_REG_RGM, @SUB_REG16_RGM16
DW @PREF, @PREF, @PREF, @PREF
DW @UNCNOWN
; Сообщение о формате текущей команды
T_STR DW $JNE
DW $PUSH_REG, $PUSH_REG, $PUSH_REG, $PUSH_REG, $PUSH_REG, $PUSH_REG, $PUSH_REG, $PUSH_REG
DW $PUSH_RS, $PUSH_RS, $PUSH_RS, $PUSH_RS
DW $PUSH_RGM
DW $SCASB
DW $SHL_RM8_1, $SHL_RM16_1 , $SHL_RM8_CL , $SHL_RM16_CL
DB $SUB_AL_IM8, $SUB_AX_IM16, $SUB_RGM_IM, $SUB_RGM16_IM16, $SUB_RGM16_IM8, $SUB_RGM_REG, $SUB_RGM16_REG16, $SUB_REG_RGM, $SUB_REG16_RGM16
DW $PREF_ES, $PREF_CS, $PREF_SS, $PREF_DS
DW $UNCNOWN
$JNE DB ‘ : JNE’, 0FFH
$PUSH_REG DB ‘ : PUSH reg’, 0FFH
$PUSH_RS DB ‘ : PUSH SEGM REG’, 0FFH
$PUSH_RGM DB ‘ : PUSH RGM’, 0FFH
$SCASB DB ‘ : SCASB’, 0FFH
$SHL_RM8_1 DB ‘ : SHL r/m8, 1’, 0FFH
$SHL_RM8_CL DB ‘ : SHL r/m8, CL’, 0FFH
$SHL_RM16_1 DB ‘ : SHL r/m16, 1’, 0FFH
$SHL_RM16_CL DB ‘ : SHL r/m16, CL’, 0FFH
$SUB_AL_IM8 DB ‘ : SUB AL, imm8’, 0FFH
$SUB_AX_IM16 DB ‘ : SUB AX, imm16’, 0FFH
$SUB_RGM_IM DB ‘ : SUB r/m8, imm8’, 0FFH
$SUB_RGM16_IM16 DB ‘ : SUB r/m16, imm16’, 0FFH
$SUB_RGM16_IM8 DB ‘ : SUB r/m16, imm8’, 0FFH
$SUB_RGM_REG DB ‘ : SUB r/m8, r8’, 0FFH
$SUB_RGM16_REG16 DB ‘ : SUB r/m16, r16’, 0FFH
$SUB_REG_RGM DB ‘ : SUB r8, r/m8’, 0FFH
$SUB_REG16_RGM16 DB ‘ : SUB r16, r/m16’, 0FFH
$PREF_ES DB ‘ : Префикс ES’, 0FFH
$PREF_CS DB ‘ : Префикс CS’, 0FFH
$PREF_SS DB ‘ : Префикс SS’, 0FFH
$PREF_DS DB ‘ : Префикс DS’, 0FFH
$UNCNOWN DB ‘ : Неизвестный КОП’, 0FFH
ERR_ADR DB ‘ Недопустимый адрес ‘, 0
;========================= ПРОЦЕДУРЫ МОДЕЛИРОВАНИЯ =========================
.CODE
; Макрос заполнения строки LINE от позиции POS содержимым CNT объектов,
; адресуемых адресом ADR при ширине поля вывода WFLD
FILL MACRO LINE, POS, CNT, ADR, WFLD
LEA DI, LINE
ADD DI, POS
MOV CX, CNT
LEA SI, ADR
MOV BP, WFLD
CALL FFILL
ENDM
; ========= ОСНОВНАЯ ПРОГРАММА ==============
MAIN PROC FAR
START: MOV AX,@DATA ;устанавливаем сегментные регистры
MOV DS,AX
MOV ES,AX
;CALL P_TEST1
; Копирование теста в сегмент кода
INI_TST: CALL COPY_TEST
; ЭМУЛЯЦИЯ
MOV _IP, 0
LEA DX, START_MSG
CALL PUTS
; Основной цикл выполнения команд
M_LOOP: CALL SHOW ; Показать нутро 8086
CALL GET_BCMD ; Взять первый байт команды в AL
CMP AL, 0F4H ; Если команда HLT
JE EXIT ; то выход по останову
MOV _COP, AL
; Найти КОП в таблице кодов операций
LEA DI, T_COP
MOV CX, SZ_TCOP
REPNE SCASB ; Поиск КОП в таблице
JZ F_COP
INC DI
F_COP: SUB DI, OFFSET T_COP+1 ; Получить индекс найденного элемента
SAL DI, 1 ; Получить смещение к элементам таблицы
; Сформировать и вывести строку сообщения о формате команды
PUSH DI
MOV DI, T_STR[DI]
MOV CX, 1
LEA SI, _COP
MOV BP, 2
CALL FFILL
POP DI
MOV DX, T_STR[DI]
CALL PUTS
; Взять сигнал STEP/EXIT/NUMTEST от пользователя и обработать ввод
CALL GETCH
CMP AL, 27
JE EXIT
CMP AL, 13
JE STEP
MOV AH, 0
AND AL, 0FH
CMP AL, 9
JLE SET_TST
MOV AL, AH
SET_TST:MOV NUMTEST, AX
JMP INI_TST
STEP: CALL WORD PTR T_LBL[DI] ; Выполнить моделирование одной команды 8086
JMP SHORT M_LOOP
EXIT: LEA DX, TERM_MSG
CALL PUTS
MOV AH,4CH ;завершаем программу и
MOV AL,0 ; выходим
INT 21H ; в DOS
MAIN ENDP
;***********************************************************
; МОДЕЛИ КОМАНД *
;***********************************************************
@JNE PROC
CALL GET_BCMD ; получаем смещение
MOV AH, 0
CMP BYTE PTR _ZF, 0
JNE @JNE_EXIT ;если _ZF не равно 0, то выходим
ADD WORD PTR _IP, AX ; иначе прибавляем смещение
@JNE_EXIT:
JMP CMD_RET
@JNE ENDP
@PUSH_REG PROC
; уменьшаем на 2 указатель стека модели
DEC WORD PTR _SP
DEC WORD PTR _SP
; вычисляем физ. адрес хоста куда указывает _SP
MOV SI, _SS
SHL SI, 4
ADD SI, _SP
LEA SI, MEM[SI]
; вычисляем физ. адрес хоста где расположен регистр-операнд модели
MOV BL, _COP
AND BL, 111b
SHL BL, 1
MOV BH, 0
ADD BX, OFFSET _AX
MOV BX, [BX]
MOV [SI], BX ; считывем данные по адресу регистра-операнда и записываем их по адресу куда указывает _SP
JMP CMD_RET
@PUSH_REG ENDP
@PUSH_RS PROC
; уменьшаем на 2 указатель стека модели
DEC WORD PTR _SP
DEC WORD PTR _SP
; вычисляем физ. адрес хоста куда указывает _SP
MOV SI, _SS
SHL SI, 4
ADD SI, _SP
LEA SI, MEM[SI]
; считывем данные по адресу регистра-операнда и записываем их по адресу куда указывает _SP
MOV BL, _COP
AND BL, 11000b
SHR BL, 2
MOV BH, 0
ADD BX, OFFSET _ES
MOV BX, [BX]
MOV [SI], BX
JMP CMD_RET
@PUSH_RS ENDP
@PUSH_RGM PROC
CALL P_MODREGRM ; Обрабатываем пост байт с помощью P_MODREGRM. Физический адрес операнда теперь в A_MODRM
; уменьшаем на 2 указатель стека модели
DEC WORD PTR _SP
DEC WORD PTR _SP
; вычисляем физ. адрес хоста куда указывает _SP
MOV SI, _SS
SHL SI, 4
ADD SI, _SP
LEA SI, MEM[SI]
MOV BX, A_MODRM
MOV BX, [BX]
;Считывем данные по вычисленному адресу операнда и записываем их по вычисленному физическому адресу куда указывает _SP
MOV [SI], BX
JMP CMD_RET
@PUSH_RGM ENDP
SET_FLAGS PROC
MOV AL, 0
MOV _OF, AL
MOV _SF, AL
MOV _ZF, AL
MOV _AF, AL
MOV _PF, AL
MOV _CF, AL
LAHF
JNO INC_FLAGS_SF
INC _OF
SAHF
INC_FLAGS_SF:
JNS INC_FLAGS_ZF
INC _SF
SAHF
INC_FLAGS_ZF:
JNZ INC_FLAGS_PF
INC _ZF
SAHF
INC_FLAGS_PF:
JNP INC_FLAGS_CF
INC _PF
SAHF
INC_FLAGS_CF:
JNC INC_FLAGS_AF
INC _CF
SAHF
INC_FLAGS_AF:
TEST AH, 00010000b
JZ INC_FLAGS_END
INC _AF
INC_FLAGS_END:
RET
SET_FLAGS ENDP
@SCASB PROC
;Внутримодельный адрес операнда преобразуется в физический хост компьютера
MOV AL, _AL
MOV SI, _ES
SHL SI, 4
ADD SI, _DI
LEA SI, MEM[SI]
;Он сравнивется содержимым регистра _AL
CMP AL, [SI]
;Устанавливаются флаги результата сравнения
CALL SET_FLAGS
;Если _DF равен 0, то _DI уменьшается на 1, иначе увеличивается на 1
CMP _DF, 1
JE @SCASB_DEC
INC WORD PTR _DI ; INC
JMP @SCASB_EXIT
@SCASB_DEC:
DEC WORD PTR _DI ; DEC
@SCASB_EXIT:
JMP CMD_RET
@SCASB ENDP
@SHL_RM8_1 PROC
;Считывается и обрабатывается пост байт с помощью P_MODREGRM
CALL P_MODREGRM
MOV BX, A_MODRM
;Производится сдвиг на 1.
SHL BYTE PTR [BX], 1
; Устанавливаются флаги результата.
CALL SET_FLAGS
JMP CMD_RET
@SHL_RM8_1 ENDP
JMP CMD_RET
@SHL_RM16_1 PROC
;Считывается и обрабатывается пост байт с помощью P_MODREGRM
CALL P_MODREGRM
MOV BX, A_MODRM
;Производится сдвиг на 1.
SHL WORD PTR [BX], 1
; Устанавливаются флаги результата.
CALL SET_FLAGS
JMP CMD_RET
@SHL_RM16_1 ENDP
@SHL_RM8_CL PROC
;Считывается и обрабатывается пост байт с помощью P_MODREGRM
CALL P_MODREGRM
MOV BX, A_MODRM
MOV CL, _CL
;Производится сдвиг байта на _CL.
SHL BYTE PTR [BX], CL
; Устанавливаются флаги результата.
CALL SET_FLAGS
JMP CMD_RET
@SHL_RM8_CL ENDP
@SHL_RM16_CL PROC
;Считывается и обрабатывается пост байт с помощью P_MODREGRM
CALL P_MODREGRM
MOV BX, A_MODRM
MOV CL, _CL
;Производится сдвиг слова на _CL.
SHL WORD PTR [BX], CL
; Устанавливаются флаги результата.
CALL SET_FLAGS
JMP CMD_RET
@SHL_RM16_CL ENDP
; sub
@SUB_AL_IM8 PROC
CALL P_MODREGRM ; зачем здесь это. разве в этой команде есть MODREGRM байт?
MOV BX, A_MODRM
PUSH AL
; IM8 надо взять вручную
MOV AL, _AL
SUB AL, BYTE PTR [BX]
MOV _AL, AL
POP AL
CALL SET_FLAGS
JMP CMD_RET
@SUB_AL_IM8 ENDP
@SUB_AX_IM16 PROC
CALL P_MODREGRM ; зачем здесь это. разве в этой команде есть MODREGRM байт?
MOV BX, A_MODRM
PUSH AX
; IM16 надо взять вручную
MOV AX, _AX
SUB AL, WORD PTR [BX]
MOV _AX, AX
POP AX
CALL SET_FLAGS
JMP CMD_RET
@SUB_AX_IM16 ENDP
; вроде правильно
@SUB_RGM_IM PROC
CALL P_MODREGRM
MOV DI, A_MODRM
CALL GET_BCMD
SUB BYTE PTR [DI], AL
CALL SET_FLAGS
JMP CMD_RET
@SUB_RGM_IM ENDP
@SUB_RGM16_IM16 PROC
CALL P_MODREGRM
MOV DI, A_MODRM
CALL GET_BCMD ; IM16 у тебя 2 байта, а ты один только считываешь
SUB WORD PTR [DI], AX
CALL SET_FLAGS
JMP CMD_RET
@SUB_RGM16_IM16 ENDP
;вроде правильно
@SUB_RGM16_IM8 PROC
CALL P_MODREGRM
MOV DI, A_MODRM
CALL GET_BCMD
SUB WORD PTR [DI], AL
CALL SET_FLAGS
JMP CMD_RET
@SUB_RGM16_IM8 ENDP
@SUB_RGM_REG PROC
CALL P_MODREGRM
MOV DI, A_MODRM
CALL GET_BCMD ; это тут зачем? адрес регистра в A_REG
SUB BYTE PTR [DI], AL
CALL SET_FLAGS
JMP CMD_RET
@SUB_RGM_REG ENDP
@SUB_RGM16_REG16 PROC
CALL P_MODREGRM
MOV DI, A_MODRM
CALL GET_BCMD ; это тут зачем? адрес регистра в A_REG
SUB WORD PTR [DI], AX
CALL SET_FLAGS
JMP CMD_RET
@SUB_RGM16_REG16 ENDP
;тоже не то. см. пред. команду
@SUB_REG_RGM PROC
CALL P_MODREGRM
MOV BX, A_MODRM
CALL GET_BCMD
SUB BYTE PTR [BX], AL
CALL SET_FLAGS
JMP CMD_RET
@SUB_REG_RGM ENDP
;тоже не то. см. пред. команду
@SUB_REG16_RGM16 PROC
CALL P_MODREGRM
MOV BX, A_MODRM
CALL GET_BCMD
SUB WORD PTR [BX], AX
CALL SET_FLAGS
JMP CMD_RET
@SUB_REG16_RGM16 ENDP
; префикс
@PREF PROC
MOV BL, _COP
SHR BL, 2
AND BL, 6
MOV BH, 0
ADD BX, OFFSET _ES
MOV [P_SEGREG], BX
INC FL_PREF ; Взвести флаг префикса
RET
@PREF ENDP
@UNCNOWN PROC
; Общий выход со сбросом флага префикса и признака SW
CMD_RET: MOV FL_PREF, 0
MOV SW, 0
RET
@UNCNOWN ENDP
;*************************************************************************
; ОБРАБОТКА АДРЕСНОГО БАЙТА, СИДЯЩЕГО В AX *
; Все операции с использованием результатов обработки адресного байта *
; выполняются, опираясь на значения указателей MAOP1 и MAOP2, адресующие *
; объекты модели I8086. Например, сложение будет выполнятся так: *
; *MAOP1 += *MAOP2 *
; В ПП P_MODRM постбайт обрабатывается так, что в A_MODRM формируется *
; указатель на объект модели, адресуемый полем MOD_RM *
;*************************************************************************
; Взять и обработать ModRegRM
P_MODREGRM PROC
CALL GET_BCMD ; Взять постбайт
MOV _PBYTE, AL ; и положить на свое место
CALL P_MODRM ; Обработать поля ModRM
JC E_ADR
MOV AL, _PBYTE
CALL P_REG ; Обработать поле REG
E_ADR: RET
P_MODREGRM ENDP
; Обработать ModRM
P_MODRM PROC
PUSH DI
PUSH BX
; ВЫДЕЛИТЬ MOD
MOV BL, AL
AND BL, 0C0H
CMP BL, 0C0H ; ЕСЛИ MOD == 11
JNE F_EA ; ТО формировать EA
CALL SET_RADR ; Сформировать в DI адрес регистра с номером в DI
MOV A_MODRM, AX ; Модельный адрес и есть адрес регистра
CLC
JMP EA_RET
; Сформировать EA
F_EA: MOV DI, AX
AND DI, 0C0H ; Выделить MOD
AND AX, 7 ; Выделить RM
SHL AX, 3 ; Придвинуть RM к MOD
ADD DI, AX ; Получить в DI смещение к элементу таблицы адресации
MOV AX, 0 ; !!! добавлен сброс регистра
CMP T_ADR[DI], @NODISP ; ЕСЛИ нет смещения
JE EA_BR ; ТО идти к обработке базового регистра
CMP T_ADR[DI], @DISP8 ; ЕСЛИ смещения размером в байт
JE EA_D8
CALL GET_WCMD ; Взять смещение D16
JMP SHORT EA_BR
EA_D8: CALL GET_BCMD ; Взять смещение D8
CBW ; и расширить его до слова
EA_BR: MOV BX, T_ADR[DI+2] ; + БазРег
ADD AX, [BX]
MOV BX, T_ADR[DI+4] ; + ИндРег
ADD AX, [BX]
; Найтем физический адрес с учетом сегментных регистров
CMP FL_PREF, 1
JE EA_PREF
MOV BX, T_ADR[DI+6] ; Взять адрес сегм. регистра из таблицы
JMP SHORT ADD_SR
EA_PREF:MOV BX, P_SEGREG ; Взять адрес сег.рег. из префикса замены
ADD_SR: MOV BX, [BX]
SAL BX, 4 ; Получить
ADD BX, AX ; физический адрес в BX
; Проверить, не выводит ли ФизАдр за границы модели памяти
CMP BX, SZ_MEM
JNA FA_NORM
LEA DX, ERR_ADR ; Сообщить об ощибке адреса
CALL PUTS
STC
JMP SHORT EA_RET ; Выход с передачей флага ошибки через CF
; Для нормального физ.адреса формируем модельный адрес
FA_NORM:LEA BX, MEM[BX]
MOV A_MODRM, BX
CLC
EA_RET: POP BX
POP DI
RET
P_MODRM ENDP
; Сформировать модельный адрес объекта, заданного полем REG
P_REG PROC
SHR AX, 3
CALL SET_RADR
MOV A_REG, AX
RET
P_REG ENDP
; Сформировать в AX адрес объекта, указываемого полем REG или RM
; В трех младших разрядах AX сидит содержимое этого поля
SET_RADR PROC
AND AX, 7
MOV DX, AX
SHL AX, 1
ADD AX, OFFSET _AX
TEST _COP, 1 ; ЕСЛИ обрабатывается Слово
JNZ SHORT R_RET ; ТО модельный адрес получен
R_BYTE: TEST DL, 4 ; ИНАЧЕ ЕСЛИ номер регистра адресует AL..BL
JE R_RET ; ТО все уже готово
SUB AX, 7 ; ИНАЧЕ смещаем адрес от BP..SI к AH..BH
R_RET: RET
SET_RADR ENDP
; записывает в AL следующий байт инструкции без изменения _IP
SEE_BCMD PROC
MOV SI, _CS
MOV CX, 4
SAL SI, CL
ADD SI, _IP
MOV AL, [SI]
RET
SEE_BCMD ENDP
; Взять очередной байт команды в AL
GET_BCMD PROC
MOV SI, _CS
MOV CX, 4
SAL SI, CL
ADD SI, _IP
MOV AL, [SI]
INC _IP
RET
GET_BCMD ENDP
; Взять очередное слово команды в AX
GET_WCMD PROC
CALL GET_BCMD
MOV AH, AL
CALL GET_BCMD
XCHG AH, AL
RET
GET_WCMD ENDP
; Копирование теста NUMTEST в модель
COPY_TEST PROC
CLD
MOV AX, DS ; Сохранить DS
MOV SI, NUMTEST ; <—
SAL SI, 1 ; | Взять адрес теста NUMTEST
MOV SI, AP_TEST[SI] ; <—
LEA DI, SEG_CS ; Область объектов начинается с SEG_CS
MOV CX, SZ_OBJ86 ; Заполнить все объекты модели I8086
PUSH CS ; Перенести CS в DS,
POP DS ; т.к. тесты объявлены в сегменте кода
REP MOVSB ; Копирование DS:SI => ES:DI
MOV DS, AX
RET
COPY_TEST ENDP
;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
; $
; ПОКАЗ СОДЕРЖИМОГО ОБЪЕКТОВ МОДЕЛИ $
; $
;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
.DATA
; Строки экранной области
SCREEN DW LINE1 ; сначала разделительная черта
DW LINE2 ; заголовок с именами регистров и флагов
DW LINE3 ; содержимое регистров и флагов
DW LINE4 ; разделительная черта
DW LINE5 ; заголовок для памяти
DW LINE6 ; разделительная черта
DW LINE7 ; mem[0..15]
DW LINE8 ; mem[16..31]
DW LINE9 ; mem[32..47]
DW LINE10 ; mem[48..63]
DW LINE11 ; mem[64..79]
DW LINE12 ; рамка
HW_MEM = 24; Половина ширины зоны содержимого памяти на экране
; Обстановка экранной области
LINE1 DB ‘г=============================================================T===============¬’,0
LINE2 DB ‘¦ AX CX DX BX SP BP SI DI ES CS SS DS ¦ O D S Z A P C ¦’,0
LINE3 DB ‘¦’, 61 DUP(‘ ‘), ‘¦ ¦’,0
LINE4 DB ‘¦======T=================================================T====¦===============¦’,0
LINE5 DB ‘¦ Адр ¦ 00 01 02 03 04 05 06 07-08 09 0A 0B 0C 0D 0E 0F ¦ IP = ¦’,0
LINE6 DB ‘¦——+————————————————-+———————¦’,0
LINE7 DB ‘¦ 0000 ¦’,HW_MEM DUP (‘ ‘),’-‘,HW_MEM DUP (‘ ‘), ‘¦ Сегмент CS ¦’,0
LINE8 DB ‘¦ 0010 ¦’,HW_MEM DUP (‘ ‘),’-‘,HW_MEM DUP (‘ ‘), ‘¦ ¦’,0
LINE9 DB ‘¦ 0020 ¦’,HW_MEM DUP (‘ ‘),’-‘,HW_MEM DUP (‘ ‘), ‘¦ Сегмент DS ¦’,0
LINE10 DB ‘¦ 0030 ¦’,HW_MEM DUP (‘ ‘),’-‘,HW_MEM DUP (‘ ‘), ‘¦ Сегмент ES ¦’,0
LINE11 DB ‘¦ 0040 ¦’,HW_MEM DUP (‘ ‘),’-‘,HW_MEM DUP (‘ ‘), ‘¦ Сегмент SS ¦’,0
LINE12 DB ‘L======¦=================================================¦====================-‘,0
.CODE
; Подпрограмма заполнения поля с 16-й распаковкой
.DATA
H_TBL DB ‘0123456789ABCDEF’
.CODE
FFILL PROC NEAR
CMP BP, 4 ; 4 символа требуют брать слово, а не байт
JE WFLD_4
MOV AH, [SI]
INC SI
CMP BP, 1
JNE FF1
SAL AH, 4
JMP SHORT FF1
WFLD_4: MOV AX, [SI]
ADD SI, 2
FF1: PUSH CX
MOV CX, BP
CALL TO_HEX ; распаковать в 16-ричное
POP CX
INC DI ; пропустить пробел между цифрами
LOOP FFILL ; Цикл по отображаемым объектам
RET
FFILL ENDP
; Распаковка CX старших полубайтов регистра AX в шестнадцатиричное
; и вывод из в массив от указателя DI
TO_HEX PROC NEAR
PUSH DX
MOV DX, AX
XOR AX, AX
PUSH BX
LEA BX, H_TBL
Hh2: ; Цикл распаковки CX байт
ROL DX, 4
MOV AX, DX
AND AX, 0FH
XLAT
MOV [DI], AL
INC DI
LOOP Hh2
POP BX
POP DX
RET
TO_HEX ENDP
; Показ всех объектов 8086
SHOW PROC NEAR
; Сначала заполнить строки отображения содержимого объектов данных
; Строка Поз Колич Адр Разм
FILL LINE3, 2, 12, _AX, 4
FILL LINE3, 64, 7, _OF, 1
FILL LINE5, 64, 1, _IP, 4
FILL LINE7, 9, 16, SEG_CS, 2
FILL LINE8, 9, 16, SEG_CS+16, 2
FILL LINE9, 9, 16, SEG_DS, 2
FILL LINE10, 9, 16, SEG_ES, 2
FILL LINE11, 9, 16, SEG_SS, 2
LEA BX, SCREEN
MOV CX, 12
Sh2: MOV DX, [BX]
CALL PUTS
ADD BX, 2
LOOP Sh2
RET
SHOW ENDP
; Подпрограмма вывода строки *DX на экране
PUTS PROC NEAR
PUSH SI
MOV SI,DX
PUTS_L: MOV AL, [SI]
CMP AL, 0FFH
JE PUTS_R
CMP AL, 0
JZ END_S
CALL PUTC
INC SI
JMP SHORT PUTS_L
; Переход на следующую строку
END_S: MOV AL, 13
CALL PUTC
MOV AL, 10
CALL PUTC
PUTS_R: POP SI
RET
PUTS ENDP
; Вывод AL на терминал
PUTC PROC NEAR
PUSH DX
MOV DL, AL
MOV AH, 2
INT 21H
POP DX
RET
PUTC ENDP
; Ввод в AL с терминала(с предварительной очисткой буфера)
GETCH PROC NEAR
MOV AH, 0CH
MOV AL, 1
INT 21H
RET
GETCH ENDP
END START

Flat Assembler — 2.1.1 Инструкции перемещения данных

Инструкция mov передает байт, слово или двойное слово из операнда- источника в операнд-назначение. Она может передавать данные между регистрами общего назначения, из регистра общего назначения в память или из памяти в этот регистр, но она не может перемещать данные из памяти в память. Также она может передать непосредственное значение в регистр общего назначения или память, сегментный регистр в регистр общего назначения или память, регистр общего назначения или память в сегментный регистр, управляющий или отладочный регистр в регистр общего назначения и регистр общего назначения в управляющий или отладочный регистр. Инструкция mov может быть скомпилирована только в случае, если размер операнда-источника совпадает с размером операнда-назначения. Ниже приведены примеры для каждой допустимой комбинации:

mov bx,ax ; из регистра общего назначения в регистр общего назначения

mov [char],al ; из регистра общего назначения в память

mov bl,[char] ; из памяти в регистр общего назначения

mov dl,32 ; непосредственное значение в регистр общего назначения

mov [char],32 ; непосредственное значение в память

mov ax,ds ; из сегментного регистра в регистр общего назначения

mov [bx],ds ; из сегментного регистра в память

mov ds,ax ; из регистра общего назначения в сегментный регистр

mov ds,[bx] ; из памяти в сегментный регистр

mov eax,cr0 ; из управляющего регистра в регистр общего назначения

mov cr3,ebx ; из регистра общего назначения в управляющий регистр

Инструкция xchg обменивает содержимое двух операндов. Она может поменять значения операндов с одинаковым размером: два байта, два слова или два двойных слова. Порядок операндов не важен. Операндами могут выступать два регистра общего назначения или регистр общего назначения и память. Например:

xchg ax,bx ; обменивает значения двух регистров общего назначения

xchg al,[char] ; обменивает значения регистра общего назначения и памяти

Инструкция push уменьшает значение регистра-указателя стека (регистр sp/esp в зависимости от разрядности режима 16 или 32-битный) на 2 или 4 (размер адреса), а затем передает операнд на вершину стека, на который указывает регистр sp/esp. Операндом может быть память, регистр общего назначения, сегментный регистр или непосредственное значение размером в слово или двойное слово. Если операндом является непосредственное значение и его размер не определен, то по умолчанию в 16-битном режиме он обрабатывается как слово, а в 32-битном режиме как двойное слово. Инструкции pushw и pushd ‑ мнемоники вариантов этой инструкции, в которых сохраняют соответственно значения в размере слова или двойного слова. Если в одной строке содержится несколько операндов (разделенных только пробелами, а не запятыми), то компилятор будет компилировать последовательность инструкций push с этими операндами. Примеры этой инструкции с одним операндом:

push ax ; сохраняет регистр общего назначения

push es ; сохраняет сегментный регистр

pushw [bx] ; сохраняет память

push 1000h ; сохраняет непосредственное значение

Инструкция pusha сохраняет содержимое восьми регистров общего назначения в стек. Соответственно эта инструкция уменьшает значение указателя стека sp/esp на 16/32. Эта команда не имеет операндов. Существуют две версии этой инструкции, одна 16-битная, а вторая 32-битная, ассемблер автоматически генерирует нужную версию для текущего режима, но это можно изменить с помощью инструкций pushaw или pushad, позволяющих всегда получать 16-битные или 32-битных версии. 16-битная версия этой инструкции помещает регистры общего назначения в стек в следующем порядке: ax, cx, dx, bx, sp, bp, si и di. В стек помещается содержимое регистра sp на момент до выполнения команды. 32-битная версия помещает в стек эквивалентные 32-битные регистры общего назначения в том же порядке.

Инструкция pop перемещает слово или двойное слово из текущей вершины стека в операнд-назначение, а после увеличивает регистр esp, так чтобы он указывал на новую вершину стека. Операндом может быть память, регистр общего назначения или сегментный регистр. Мнемоники popw и popd ‑ это варианты этой инструкции, восстанавливающие соответственно слова и двойные слова. Если на одной строке находится несколько операндов, разделенных пробелами, ассемблер компилирует последовательность инструкций с этими операндами.

pop bx ; восстанавливает регистр общего назначения

pop ds ; восстанавливает сегментный регистр

popw [si] ; восстанавливает память

Инструкция popa восстанавливает регистры, сохраненные в стек инструкцией pusha, кроме сохраненного значения sp/esp, который игнорируется. У этой инструкции нет операндов. Чтобы компилировать 16-битную или 32-битную версию этой инструкции, используйте мнемоники popaw или popad.

Шпаргалка по основным инструкциям ассемблера x86/x64

В прошлой статье мы написали наше первое hello world приложение на асме, научились его компилировать и отлаживать, а также узнали, как делать системные вызовы в Linux. Сегодня же мы познакомимся непосредственно с ассемблерными инструкциями, понятием регистров, стека и вот этого всего. Ассемблеры для архитектур x86 (a.k.a i386) и x64 (a.k.a amd64) очень похожи, в связи с чем нет смысла рассматривать их в отдельных статьях. Притом акцент я постараюсь делать на x64, попутно отмечая отличия от x86, если они есть. Далее предполагается, что вы уже знаете, например, чем стек отличается от кучи, и объяснять такие вещи не требуется.

Регистры общего назначения

Регистр — это небольшой (обычно 4 или 8 байт) кусочек памяти в процессоре с чрезвычайно большой скоростью доступа. Регистры делятся на регистры специального назначения и регистры общего назначения. Нас сейчас интересуют регистры общего назначения. Как можно догадаться по названию, программа может использовать эти регистры под свои нужды, как ей вздумается.

На x86 доступно восемь 32-х битных регистров общего назначения — eax, ebx, ecx, edx, esp, ebp, esi и edi. Регистры не имеют заданного наперед типа, то есть, они могут трактоваться как знаковые или беззнаковые целые числа, указатели, булевы значения, ASCII-коды символов, и так далее. Несмотря на то, что в теории эти регистры можно использовать как угодно, на практике обычно каждый регистр используется определенным образом. Так, esp указывает на вершину стека, ecx играет роль счетчика, а в eax записывается результат выполнения операции или процедуры. Существуют 16-и битные регистры ax, bx, cx, dx, sp, bp, si и di, представляющие собой 16 младших бит соответствующих 32-х битных регистров. Также доступны и 8-и битовые регистры ah, al, bh, bl, ch, cl, dh и dl, которые представляют собой старшие и младшие байты регистров ax, bx, cx и dx соответственно.

Рассмотрим пример. Допустим, выполняются следующие три инструкции:

(gdb) x/3i $pc
=> 0x8048074: mov    $0xaabbccdd,%eax
   0x8048079: mov    $0xee,%al
   0x804807b: mov    $0x1234,%ax

Значения регистров после записи в eax значения 0xAABBCCDD:

(gdb) p/x $eax
$1 = 0xaabbccdd
(gdb) p/x $ax
$2 = 0xccdd
(gdb) p/x $ah
$3 = 0xcc
(gdb) p/x $al
$4 = 0xdd

Значения после записи в регистр al значения 0xEE:

(gdb) p/x $eax
$5 = 0xaabbccee
(gdb) p/x $ax
$6 = 0xccee
(gdb) p/x $ah
$7 = 0xcc
(gdb) p/x $al
$8 = 0xee

Значения регистров после записи в ax числа 0x1234:

(gdb) p/x $eax
$9 = 0xaabb1234
(gdb) p/x $ax
$10 = 0x1234
(gdb) p/x $ah
$11 = 0x12
(gdb) p/x $al
$12 = 0x34

Как видите, ничего сложного.

Примечание: Синтаксис GAS позволяет явно указывать размеры операндов путем использования суффиксов b (байт), w (слово, 2 байта), l (длинное слово, 4 байта), q (четверное слово, 8 байт) и некоторых других. Например, вместо команды mov $0xEE, %al можно написать movb $0xEE, %al, вместо mov $0x1234, %axmovw $0x1234, %ax, и так далее. В современном GAS эти суффиксы являются опциональными и я лично их не использую. Но не пугайтесь, если увидите их в чужом коде.

На x64 размер регистров был увеличен до 64-х бит. Соответствующие регистры получили название rax, rbx, и так далее. Кроме того, регистров общего назначения стало шестнадцать вместо восьми. Дополнительные регистры получили названия r8, r9, …, r15. Соответствующие им регистры, которые представляют младшие 32, 16 и 8 бит, получили название r8d, r8w, r8b, и по аналогии для регистров r9-r15. Кроме того, появились регистры, представляющие собой младшие 8 бит регистров rsi, rdi, rbp и rsp — sil, dil, bpl и spl соответственно.

Про адресацию

Как уже отмечалось, регистры могут трактоваться, как указатели на данные в памяти. Для разыменования таких указателей используется специальный синтаксис:

Эта запись означает «прочитай 8 байт по адресу, записанному в регистре rsp, и сохрани их в регистр rax». При запуске программы rsp указывает на вершину стека, где хранится число аргументов, переданных программе (argc), указатели на эти аргументы, а также переменные окружения и кое-какая другая информация. Таким образом, в результате выполнения приведенной выше инструкции (разумеется, при условии, что перед ней не выполнялось каких-либо других инструкций) в rax будет записано количество аргументов, с которыми была запущена программа.

В одной команде можно указывать адрес и смешение (как положительное, так и отрицательное) относительно него:

Эта запись означает «возьми rsp, прибавь к нему 8, прочитай 8 байт по получившемуся адресу и положи их в rax». Таким образом, в rax будет записан адрес строки, представляющей собой первый аргумент программы, то есть, имя исполняемого файла.

При работе с массивами бывает удобно обращаться к элементу с определенным индексом. Соответствующий синтаксис:

# инструкция xchg меняет значения местами
xchg 16(%rsp,%rcx,8), %rax

Читается так: «посчитай rcx*8 + rsp + 16, и поменяй местами 8 байт (размер регистра) по получившемуся адресу и значение регистра rax». Другими словами, rsp и 16 все так же играют роль смещения, rcx играет роль индекса в массиве, а 8 — это размер элемента массива. При использовании данного синтаксиса допустимыми размерами элемента являются только 1, 2, 4 и 8. Если требуется какой-то другой размер, можно использовать инструкции умножения, бинарного сдвига и прочие, которые мы рассмотрим далее.

Наконец, следующий код тоже валиден:

.data
msg:
  .ascii «Hello, world!\n»
.text

.globl _start
_start:
  # обнуление rcx
  xor %rcx, %rcx
  mov msg(,%rcx,8), %al
  mov msg, %ah

В смысле, что можно не указывать регистр со смещением или вообще какие-либо регистры. В результате выполнения этого кода в регистры al и ah будет записан ASCII-код буквы H, или 0x48.

В этом контексте хотелось бы упомянуть еще одну полезную ассемблерную инструкцию:

# rax := rcx*8 + rax + 123
lea 123(%rax,%rcx,8), %rax

Инструкция lea очень удобна, так как позволяет сразу выполнить умножение и несколько сложений.

Fun fact! На x64 в байткоде инструкций никогда не используются 64-х битовые смещения. В отличие от x86, инструкции часто оперируют не абсолютными адресами, а адресами относительно адреса самой инструкции, что позволяет обращаться к ближайшим +/- 2 Гб оперативной памяти. Соответствующий синтаксис:

Сравним длины опкодов «обычного» и «относительного» mov (objdump -d):

4000b0: 8a 0c 25 e8 00 60 00  mov    0x6000e8,%cl
4000b7: 8a 05 2b 00 20 00     mov    0x20002b(%rip),%al # 0x6000e8

Как видите, «относительный» mov еще и на один байт короче! Что это за регистр такой rip мы узнаем чуть ниже.

Для записи же полного 64-х битового значения в регистр предусмотрена специальная инструкция:

movabs $0x1122334455667788, %rax

Другими словами, процессоры x64 так же экономно кодируют инструкции, как и процессоры x86, и в наше время нет особо смысла использовать процессоры x86 в системах, имеющих пару гигабайт оперативной памяти или меньше (мобильные устройства, холодильники, микроволновки, и так далее). Скорее всего, процессоры x64 будут даже более эффективны за счет большего числа доступных регистров и большего размера этих регистров.

Арифметические операции

Рассмотрим основные арифметические операции:

# инциализируем значения регистров
mov  $123, %rax
mov  $456, %rcx

# инкремент: rax = rax + 1 = 124
inc  %rax

# декремент: rax = rax — 1 = 123
dec  %rax

# сложение: rax = rax + rcx = 579
add  %rcx, %rax

# вычитание: rax = rax — rcx = 123
sub  %rcx, %rax

# изменение знака: rcx = — rcx = -456
neg  %rcx

Здесь и далее операндами могут быть не только регистры, но и участки памяти или константы. Но оба операнда не могут быть участками памяти. Это правило применимо ко всем инструкциям ассемблера x86/x64, по крайней мере, из рассмотренных в данной статье.

Пример умножения:

mov $100, %al
mov $3, %cl
mul %cl

В данном примере инструкция mul умножает al на cl, и сохраняет результат умножения в пару регистров al и ah. Таким образом, ax примет значение 0x12C или 300 в десятичной нотации. В худшем случае для сохранения результата перемножения двух N-байтовых значений может потребоваться до 2*N байт. В зависимости от размера операнда результат сохраняется в al:ah, ax:dx, eax:edx или rax:rdx. Притом в качестве множителей всегда используется первый из этих регистров и переданный инструкции аргумент.

Знаковое умножение производится точно так же при помощи инструкции imul. Кроме того, существуют варианты imul с двумя и тремя аргументами:

mov  $123, %rax
mov  $456, %rcx

# rax = rax * rcx = 56088
imul %rcx, %rax

# rcx = rax * 10 = 560880
imul $10, %rax, %rcx

Инструкции div и idiv производят действия, обратные mul и imul. Например:

mov  $0,   %rdx
mov  $456, %rax
mov  $123, %rcx

# rax = rdx:rax / rcx = 3
# rdx = rdx:rax % rcx = 87
div  %rcx

Как видите, был получен результат целочисленного деления, а также остаток от деления.

Это далеко не все арифметические инструкции. Например, есть еще adc (сложение с учетом флага переноса), sbb (вычитание с учетом займа), а также соответствующие им инструкции, выставляющие и очищающие соответствующие флаги (ctc, clc), и многие другие. Но они распространены намного меньше, и потому в рамках данной статьи не рассматриваются.

Логические и битовые операции

Как уже отмечалось, особой типизации в ассемблере x86/x64 не предусмотрено. Поэтому не стоит удивляться, что в нем нет отдельных инструкций для выполнения булевых операций и отдельных для выполнения битовых операций. Вместо этого есть один набор инструкций, работающих с битами, а уж как интерпретировать результат — решает конкретная программа.

Так, например, выглядит вычисление простейшего логического выражения:

mov  $0, %rax # a = false
mov  $1, %rbx # b = true
mov  $0, %rcx # c = false

# rdx := a || !(b && c)
mov  %rcx, %rdx  # rdx = c
and  %rbx, %rdx  # rdx &= b
not  %rdx        # rdx = ~ rdx
or   %rax, %rdx  # rdx |= a
and  $1,   %rdx  # rdx &= 1

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

Еще одна полезная инструкция — это xor (исключающее или). В логических выражениях xor используется нечасто, однако с его помощью часто происходит обнуление регистров. Если посмотреть на опкоды инструкций, то становится понятно, почему:

  4000b3: 48 31 db              xor    %rbx,%rbx
  4000b6: 48 ff c3              inc    %rbx
  4000b9: 48 c7 c3 01 00 00 00  mov    $0x1,%rbx

Как видите, инструкции xor и inc кодируются всего лишь тремя байтами каждая, в то время, как делающая то же самое инструкция mov занимает целых семь байт. Каждый отдельный случай, конечно, лучше бенчмаркать отдельно, но общее эвристическое правило такое — чем короче код, тем больше его помещается в кэши процессора, тем быстрее он работает.

В данном контексте также следует вспомнить инструкции побитового сдвига, тестирования битов (bit test) и сканирования битов (bit scan):

# положим что-нибудь в регистр
movabs $0xc0de1c0ffee2beef, %rax

# сдвиг влево на 3 бита
# rax = 0x0de1c0ffee2beef0
shl $4,  %rax

# сдвиг вправо на 7 бит
# rax = 0x001bc381ffdc57dd
shr $7,  %rax

# циклический сдвиг вправо на 5 бит
# rax = 0xe800de1c0ffee2be
ror $5,  %rax

# циклический сдвиг влево на 5 бит
# rax = 0x001bc381ffdc57dd
rol $5,  %rax

# положить в CF (см далее) значение 13-го бита
# CF = !!(0x1bc381ffdc57dd & (1 << 13)) = 0
bt  $13, %rax

# то же самое + установить бит (bit test and set)
# rax = 0x001bc381ffdc77dd, CF = 0
bts $13, %rax

# то же самое + сбросить бит (bit test and reset)
# rax = 0x001bc381ffdc57dd, CF = 1
btr $13, %rax

# то же самое + инвертировать бит (bit test and complement)
# rax = 0x001bc381ffdc77dd, CF = 0
btc $13, %rax

# найти самый младший ненулевой байт (bit scan forward)
# rcx = 0, ZF = 0
bsf %rax, %rcx

# найти самый старший ненулевой байт (bit scan reverse)
# rdx = 52, ZF = 0
bsr %rax, %rdx

# если все биты нулевые, ZF = 1, значение rdx неопределено
xor %rax, %rax
bsf %rax, %rdx

Еще есть битовые сдвиги со знаком (sal, sar), циклические сдвиги с флагом переноса (rcl, rcr), а также сдвиги двойной точности (shld, shrd). Но используются они не так уж часто, да и утомишься перечислять вообще все инструкции. Поэтому их изучение я оставляю вам в качестве домашнего задания.

Условные выражения и циклы

Выше несколько раз упоминались какие-то там флаги, например, флаг переноса. Под флагами понимаются биты специального регистра eflags / rflags (название на x86 и x64 соответственно). Напрямую обращаться к этому регистру при помощи инструкций mov, add и подобных нельзя, но он изменяется и используется различными инструкциями косвенно. Например, уже упомянутый флаг переноса (carry flag, CF) хранится в нулевом бите eflags / rflags и используется, например, в той же инструкции bt. Еще из часто используемых флагов можно назвать zero flag (ZF, 6-ой бит), sign flag (SF, 7-ой бит), direction flag (DF, 10-ый бит) и overflow flag (OF, 11-ый бит).

Еще из таких неявных регистров следует назвать eip / rip, хранящий адрес текущей инструкции. К нему также нельзя обращаться напрямую, но он виден в GDB вместе с eflags / rflags, если сказать info registers, и косвенно изменяется всеми инструкциям. Большинство инструкций просто увеличивают eip / rip на длину этой инструкции, но есть и исключения из этого правила. Например, инструкция jmp просто осуществляет переход по заданному адресу:

  # обнуляем rax
  xor  %rax, %rax
  jmp next
  # эта инструкция будет пропущена
  inc  %rax
next:
  inc  %rax

В результате значение rax будет равно единице, так как первая инструкция inс будет пропущена. Заметьте, что адрес перехода также может быть записан в регистре:

  xor %rax, %rax
  mov $next, %rcx
  jmp *%rcx
  inc %rax
next:
  inc %rax

Впрочем, на практике такого кода лучше избегать, так как он ломает предсказание переходов и потому менее эффективен.

Примечание: GAS позволяет давать меткам цифирные имена типа 1:, 2:, и так далее, и переходить к ближайшей предыдущей или следующей метке с заданным номером инструкциями вроде jmp 1b и jmp 1f. Это довольно удобно, так как иногда бывает трудно придумать меткам осмысленные имена. Подробности можно найти здесь.

Условные переходы обычно осуществляются при помощи инструкции cmp, которая сравнивает два своих операнда и выставляет соответствующие флаги, за которой следует инструкция из семейства je, jg и подобных:

  cmp %rax, %rcx

  je  1f # перейти, если равны (equal)
  jl  1f # перейти, если знаково меньше (less)
  jb  1f # перейти, если беззнаково меньше (below)
  jg  1f # перейти, если знаково больше (greater)
  ja  1f # перейти, если беззнаково больше (above)

1:

Существует также инструкции jne (перейти, если не равны), jle (перейти, если знаково меньше или равны), jna (перейти, если беззнаково не больше) и подобные. Принцип их именования, надеюсь, очевиден. Вместо je / jne часто пишут jz / jnz, так как инструкции je / jne просто проверяют значение ZF. Также есть инструкции, проверяющие другие флаги — js, jo и jp, но на практике они используются редко. Все эти инструкции вместе взятые обычно называют jcc. То есть, вместо конкретных условий пишутся две буквы «c», от «condition». Здесь можно найти хорошую сводную таблицу по всем инструкциям jcc и тому, какие флаги они проверяют.

Помимо cmp также часто используют инструкцию test:

  test %rax, %rax
  jz 1f # перейти, если rax == 0
  js 2f # перейти, если rax < 0
1:
  # какой-то код
2:
  # какой-то еще код

Fun fact! Интересно, что cmp и test в душе являются теми же sub и and, только не изменяют своих операндов. Это знание можно использовать для одновременного выполнения sub или and и условного перехода, без дополнительных инструкций cmp или test.

Еще из инструкций, связанных с условными переходами, можно отметить следующие.

 jrcxz  1f
  # какой-то код
1:

Инструкция jrcxz осуществляет переход только в том случае, если значение регистра rcx равно нулю.

Инструкции семейства cmovcc (conditional move) работают как mov, но только при выполнении заданного условия, по аналогии с jcc.

Инструкции setcc присваивают однобайтовому регистру или байту в памяти значение 1, если заданное условие выполняется, и 0 иначе.

Сравнить rax с заданным куском памяти. Если равны, выставить ZF и сохранить по указанному адресу значение указанного регистра, в данном примере rcx. Иначе очистить ZF и загрузить значение из памяти в rax. Также оба операнда могут быть регистрами.

cmpxchg8b (%rsi)
cmpxchg16b (%rsi)

Инструкция cmpxchg8b главным образом нужна в x86. Она работает аналогично cmpxchg, только производит compare and swap сразу 8-и байт. Регистры edx:eax используются для сравнения, а регистры ecx:ebx хранят то, что мы хотим записать. Инструкция cmpxchg16b по тому же принципу производит compare and swap сразу 16-и байт на x64.

Важно! Примите во внимание, что без префикса lock все эти compare and swap инструкции не атомарны.

mov $10, %rcx
1:
# какой-то код
  loop   1b
# loopz  1b
# loopnz 1b

Инструкция loop уменьшает значение регистра rcx на единицу, и если после этого rcx != 0, осуществляет переход на заданную метку. Инструкции loopz и loopnz работают аналогично, только условия более сложные — (rcx != 0) && (ZF == 1) и (rcx != 0) && (ZF == 0) соответственно.

Не нужно быть семи пядей во лбу, чтобы изобразить при помощи этих инструкций конструкцию if-then-else или циклы for / while, поэтому двигаемся дальше.

«Строковые» операции

Рассмотрим следующий кусок кода:

mov $str1, %rsi
mov $str2, %edi
cld
cmpsb

В регистры rsi и rdi кладутся адреса двух строк. Командой cld очищается флаг направления (DF). Инструкция, выполняющая обратное действие, называется std. Затем в дело вступает инструкция cmpsb. Она сравнивает байты (%rsi) и (%rdi) и выставляет флаги в соответствии с результатом сравнения. Затем, если DF = 0, rsi и rdi увеличиваются на единицу (количество байт в том, что мы сравнивали), иначе — уменьшаются. Аналогичные инструкции cmpsw, cmpsl и cmpsq сравнивают слова, длинные слова и четверные слова соответственно.

Инструкции cmps интересны тем, что могут использоваться с префиксом rep, repe (repz) и repne (repnz). Например:

mov $str1, %rsi
mov $str2, %edi
mov $len,  %rcx
cld
repe cmpsb
jne not_equal

Префикс rep повторяет инструкцию заданное в регистре rcx количество раз. Префиксы repz и repnz делают то же самое, но только после каждого выполнения инструкции дополнительно проверяется ZF. Цикл прерывается, если ZF = 0 в случае c repz и если ZF = 1 в случае с repnz. Таким образом, приведенный выше код проверяет равенство двух буферов одинакового размера.

Аналогичные инструкции movs перекладывает данные из буфера, адрес которого указан в rsi, в буфер, адрес которого указан в rdi (легко запомнить — rsi значит source, rdi значит destination). Инструкции stos заполняет буфер по адресу из регистра rdi байтами из регистра rax (или eax, или ax, или al, в зависимости от конкретной инструкции). Инструкции lods делают обратное действие — копируют байты по указанному в rsi адресу в регистр rax. Наконец, инструкции scas ищут байты из регистра rax (или соответствующих регистров меньшего размера) в буфере, адрес которого указан в rdi. Как и cmps, все эти инструкции работают с префиксами rep, repz и repnz.

На базе этих инструкций легко реализуются процедуры memcmp, memcpy, strcmp и подобные. Интересно, что, например, для обнуления памяти инженеры Intel рекомендуют использовать на современных процессорах rep stosb, то есть, обнулять побайтово, а не, скажем, четверными словами.

Работа со стеком и процедуры

Со стеком все очень просто. Инструкция push кладет свой аргумент на стек, а инструкция pop извлекает значение со стека. Например, если временно забыть про инструкцию xchg, то поменять местами значение двух регистров можно так:

push %rax
mov %rcx, %rax
pop %rcx

Существуют инструкции, помещающие на стек и извлекающие с него регистр rflags / eflags:

pushf
# делаем что-то, что меняет флаги
popf
# флаги восстановлены, самое время сделать jcc

А так, к примеру, можно получить значение флага CF:

pushf
pop %rax
and $1, %rax

На x86 также существуют инструкции pusha и popa, сохраняющие на стеке и восстанавливающие с него значения всех регистров. В x64 этих инструкций больше нет. Видимо, потому что регистров стало больше и сами регистры теперь длиннее — сохранять и восстанавливать их все стало сильно дороже.

Процедуры, как правило, «создаются» при помощи инструкций call и ret. Инструкция call кладет на стек адрес следующей инструкции и передает управление по указанному в аргументе адресу. Инструкция ret читает со стека адрес возврата и передает по нему управление. Например:

someproc:
  # типичный пролог процедуры
  # для примера выделяем 0x10 байт на стеке под локальные переменные
  # rbp — указатель на фрейм стека
  push %rbp
  mov %rsp, %rbp
  sub $0x10, %rsp

  # тут типа какие-то вычисления …
  mov $1, %rax

  # типичный эпилог процедуры
  add $0x10, %rsp
  pop %rbp

  # выход из процедуры
  ret

_start:
  # как и в случае с jmp, адрес перехода может быть в регистре
  call someproc
  test %rax, %rax
  jnz error

Примечание: Аналогичный пролог и эпилог можно написать при помощи инструкций enter $0x10, $0 и leave. Но в наше время эти инструкции используются редко, так как они выполняются медленнее из-за дополнительной поддержки вложенных процедур.

Как правило, возвращаемое значение передается в регистре rax или, если его размера не достаточно, записывается в структуру, адрес которой передается в качестве аргумента. К вопросу о передаче аргументов. Соглашений о вызовах существует великое множество. В одних все аргументы всегда передаются через стек (отдельный вопрос — в каком порядке) и за очистку стека от аргументов отвечает сама процедура, в других часть аргументов передается через регистры, а часть через стек, и за очистку стека от аргументов отвечает вызывающая сторона, плюс множество вариантов посередине, с отдельными правилами касательно выравнивания аргументов на стеке, передачи this, если это ООП язык, и так далее. В общем случае для произвольно взятой архитектуры, компилятора и языка программирования соглашение о вызовах может быть вообще каким угодно.

Для примера рассмотрим ассемблерный код, сгенерированный CLang 3.8 для простой программки на языке C под x64. Так выглядит одна из процедур:

unsigned int
hash(const unsigned char *data, const size_t data_len) {
  unsigned int hash = 0x4841434B;
  for(int i = 0; i < data_len; i++) {
    hash = ((hash << 5) + hash) + data[i];
  }
  return hash;
}

Дизассемблерный листинг (при компиляции с -O0, комментарии мои):

# типичный пролог процедуры
# регистр rsp не изменяется, так как процедура не вызывает никаких
# других процедур
  400950: 55                    push   %rbp
  400951: 48 89 e5              mov    %rsp,%rbp

# инициализация локальных переменных:
# -0x08(%rbp) — const unsigned char *data (8 байт)
# -0x10(%rbp) — const size_t data_len (8 байт)
# -0x14(%rbp) — unsigned int hash (4 байта)
# -0x18(%rbp) — int i (4 байта)
  400954: 48 89 7d f8           mov    %rdi,-0x8(%rbp)
  400958: 48 89 75 f0           mov    %rsi,-0x10(%rbp)
  40095c: c7 45 ec 4b 43 41 48  movl   $0x4841434b,-0x14(%rbp)
  400963: c7 45 e8 00 00 00 00  movl   $0x0,-0x18(%rbp)

# rax := i. если достигли data_len, выходим из цикла
  40096a: 48 63 45 e8           movslq -0x18(%rbp),%rax
  40096e: 48 3b 45 f0           cmp    -0x10(%rbp),%rax
  400972: 0f 83 28 00 00 00     jae    4009a0 <hash+0x50>

# eax := (hash << 5) + hash
  400978: 8b 45 ec              mov    -0x14(%rbp),%eax
  40097b: c1 e0 05              shl    $0x5,%eax
  40097e: 03 45 ec              add    -0x14(%rbp),%eax

# eax += data[i]
  400981: 48 63 4d e8           movslq -0x18(%rbp),%rcx
  400985: 48 8b 55 f8           mov    -0x8(%rbp),%rdx
  400989: 0f b6 34 0a           movzbl (%rdx,%rcx,1),%esi
  40098d: 01 f0                 add    %esi,%eax

# hash := eax
  40098f: 89 45 ec              mov    %eax,-0x14(%rbp)

# i++ и перейти к началу цикла
  400992: 8b 45 e8              mov    -0x18(%rbp),%eax
  400995: 83 c0 01              add    $0x1,%eax
  400998: 89 45 e8              mov    %eax,-0x18(%rbp)
  40099b: e9 ca ff ff ff        jmpq   40096a <hash+0x1a>

# возвращаемое значение (hash) кладется в регистр eax
  4009a0: 8b 45 ec              mov    -0x14(%rbp),%eax

# типичный эпилог
  4009a3: 5d                    pop    %rbp
  4009a4: c3                    retq

Здесь мы встретили две новые инструкции — movs и movz. Они работают точно так же, как mov, только расширяют один операнд до размера второго, знаково и беззнаково соответственно. Например, инструкция movzbl (%rdx,%rcx,1),%esi читайт байт (b) по адресу (%rdx,%rcx,1), расширяет его в длинное слово (l) путем добавления в начало нулей (z) и кладет результат в регистр esi.

Как видите, два аргумента были переданы процедуре через регистры rdi и rsi. По всей видимости, используется конвенция под названием System V AMD64 ABI. Утверждается, что это стандарт де-факто под x64 на *nix системах. Я не вижу смысла пересказывать описание этой конвенции здесь, заинтересованные читатели могут ознакомиться с полным описанием по приведенной ссылке.

Заключение

Само собой разумеется, в рамках одной статьи, описать весь ассемблер x86/x64 не представляется возможным (более того, я не уверен, что сам знаю его прямо таки весь). Как минимум, за кадром остались такие темы, как операции над числами с плавающей точкой, MMX-, SSE- и AVX-инструкции, а также всякие экзотические инструкции вроде lidt, lgdt, bswap, rdtsc, cpuid, movbe, xlatb, или prefetch. Я постараюсь осветить их в следующих статьях, но ничего не обещаю. Следует также отметить, что в выводе objdump -d для большинства реальных программ вы очень редко увидите что-то помимо описанного выше.

Еще интересный топик, оставшийся за кадром — это атомарные операции, барьеры памяти, спинлоки и вот это все. Например, compare and swap часто реализуется просто как инструкция cmpxchg с префиксом lock. По аналогии реализуется атомарный инкремент, декремент, и прочее. Увы, все это тянет на тему для отдельной статьи.

В качестве источников дополнительной информации можно рекомендовать книгу Modern X86 Assembly Language Programming, и, конечно же, мануалы от Intel. Также довольно неплоха книга x86 Assembly на wikibooks.org.

Из онлайн-справочников по ассемблерным инструкциям стоит обратить внимание на следующие:

А знаете ли вы ассемблер, и если да, то находите ли это знание полезным?

Метки: Ассемблер.

В пустоту: Ссылка на набор команд x86

* Операнды moffs8, moffs16 и moffs32 определяют простое смещение относительно базы сегмента, где 8, 16 и 32 относятся к размеру данных. Атрибут размера адреса команды определяет размер смещения: 16 или 32 бита.

** В 32-битном режиме ассемблер может вставить 16-битный префикс размера операнда с помощью этой инструкции.

Копирует второй операнд (исходный операнд) в первый операнд (целевой операнд).Исходным операндом может быть непосредственное значение, регистр общего назначения, регистр сегмента или ячейка памяти; регистр назначения может быть регистром общего назначения, регистром сегмента или ячейкой памяти. Оба операнда должны быть одинакового размера, который может быть байтом, словом или двойным словом.

Инструкцию MOV нельзя использовать для загрузки регистра CS. Попытка сделать это приводит к недопустимому исключению кода операции (#UD). Чтобы загрузить регистр CS, используйте дальнюю инструкцию JMP, CALL или RET.

Если операнд-адресат является сегментным регистром (DS, ES, FS, GS или SS), исходный операнд должен быть допустимым селектором сегмента.В защищенном режиме перемещение селектора сегмента в регистр сегмента автоматически вызывает загрузку информации дескриптора сегмента, связанной с этим селектором сегмента, в скрытую (теневую) часть регистра сегмента. При загрузке этой информации проверяется информация о селекторе сегмента и дескрипторе сегмента (см. Алгоритм «Операция» ниже). Данные дескриптора сегмента получаются из записи GDT или LDT для указанного селектора сегмента.

Селектор нулевого сегмента (значения 0000-0003) может быть загружен в регистры DS, ES, FS и GS, не вызывая исключения защиты.Однако любая последующая попытка ссылаться на сегмент, соответствующий регистр сегмента которого загружен с нулевым значением, вызывает общую исключительную ситуацию защиты (#GP), и обращение к памяти не происходит.

Загрузка регистра SS инструкцией MOV запрещает все прерывания до тех пор, пока не будет выполнена следующая инструкция. Эта операция позволяет загрузить указатель стека в регистр ESP со следующей инструкцией (MOV ESP, значение указателя стека) до того, как произойдет прерывание1. Имейте в виду, что инструкция LSS предлагает более эффективный метод загрузки регистров SS и ESP.

При работе в 32-битном режиме и перемещении данных между сегментным регистром и регистром общего назначения 32-битные процессоры IA-32 не требуют использования 16-битного префикса размера операнда (байта со значением 66H) с этой инструкцией, но большинство ассемблеров вставят ее, если используется стандартная форма инструкции (например, MOV DS, AX). Процессор выполнит эту инструкцию правильно, но обычно для этого требуются дополнительные часы. В большинстве ассемблеров, использующих форму инструкции MOV DS, EAX избегает этого ненужного префикса 66H.Когда процессор выполняет команду с 32-битным регистром общего назначения, он предполагает, что 16 младших битов регистра общего назначения являются операндом назначения или источником. Если регистр является операндом назначения, результирующее значение в двух старших байтах регистра зависит от реализации. Для процессоров семейства Pentium 4, Intel Xeon и P6 два старших байта заполнены нулями; для более ранних 32-разрядных процессоров IA-32 два старших байта не определены.

Набор команд

Набор команд


Формат стандартной программы на ассемблере

ДОССЕГ
.МОДЕЛЬ МАЛЫЙ
.STACK 4096

. ДАННЫЕ
; определения данных

.КОД
ProgramStart:
; инструкции ассемблера

mov ax, SEG _DATA; настроить сегмент данных

mov ds, топор

мов ах, 4ч; завершить программу

int 21h

КОНЕЦ ProgramStart

Это действительно только для простых программ на ассемблере, которые имеют
небольшой, нерезидентный сорт.Для больших программ .MODEL
директива может быть БОЛЬШОЙ, ОГРОМНОЙ или КОМПАКТНОЙ. Директива .STACK
указывает количество байтов, которые нужно зарезервировать для стека
программа. Если это число опущено, стек по умолчанию равен 1 КБ.

За исключением команд завершения программы, все остальные
команды — это то, что называется директивами ассемблера. Эти инструкции
ассемблер о том, как собрать программу, не генерируя
любой актуальный код ассемблера.

Все имена, которые используются в программе, конвертируются в память
локации в процессе сборки.Таким образом, любое имя обычно
считается аналогом ячейки памяти при записи
код сборки.

Определения данных

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

Имя используется для ссылки на данные после этого. Тип данных
может быть DB (байт) или DW (слово). Это определяет, что ассемблер
следует рассматривать как тип данных первого элемента данных.Все
другие элементы данных в этой строке наследуют тот же тип данных. Там
— это ряд других типов данных, но наиболее распространенными являются DB и DW.
Значение можно использовать для установки начального значения / с для элемента данных.
Это может быть установлено на «?» проинструктировать ассемблера, что
Этому элементу данных не нужно присваивать какое-либо конкретное значение.

Инструкция MOV

Инструкция MOV — самая важная команда в 8086.
потому что он перемещает данные из одного места в другое.Он также имеет
широчайшее разнообразие параметров; так что это программист на ассемблере
может эффективно использовать MOV, остальные команды проще
понимать.

формат:
MOV назначение, источник

Возможные комбинации операндов следующие:

пункт назначения источник пример
регистр регистр mov ax, bx
регистр немедленно mov ax, 10h
регистр память mov ax, es: [bx]
память немедленно mov aNumber, 10h
память регистр mov aDigit, ax

MOV копирует данные из источника в место назначения.Данные
может быть байтом или словом. Иногда это должно быть явно
заявлено, когда ассемблер не может определить по операндам,
ссылка на байт или слово.

Инструкция MOV имеет несколько ограничений:

  • немедленное значение не может быть перемещено в сегментный регистр
    напрямую (т.е. mov ds, 10)
  • сегментные регистры не могут быть скопированы напрямую (т.е. mov es, ds)
  • ячейку памяти нельзя скопировать в другую ячейку памяти
    (я.е. mov aNumber, aDigit)
  • CS невозможно скопировать (например, mov cs, ax)

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

Каждое из возможных значений для пункта назначения и источника равно
позвонил по адресу. Из приведенной выше таблицы видно, что
существует несколько различных режимов адресации (немедленная, регистровая,
объем памяти).

Режимы адресации
  1. Немедленное обращение
    Это когда постоянное значение перемещается в регистр или память.
    расположение. На самом деле это не адрес, поскольку он не указывает
    в любое место в памяти или ЦП. Немедленное обращение
    может использоваться только для источника, поскольку непосредственные значения не
    сами хранятся где угодно; во время сборки программы
    немедленное значение становится частью инструкции машинного кода.
    пример: mov ax, 10h
  2. Адресация регистров
    Регистр может использоваться как источник и адресат
    инструкция. Регистры очень быстрые для большинства операций, поэтому максимум
    необходимо использовать их.
    примеры:
    mov ax, bx
    мов топор, 10ч
    mov si, es: [bx]
  3. Прямая адресация памяти
    Ячейку памяти можно использовать, используя ее адрес в качестве операнда.
    в инструкции.
    пример: mov ax, aDigit
  4. Регистр косвенной адресации
    Вместо того, чтобы указывать ячейку памяти как операнд, память
    местоположение может быть сохранено в реестре. Тогда этот реестр должен
    быть указанным для доступа к памяти. Для косвенной адресации
    8086 может использовать только регистры BX, BP, SI и DI.
    пример: mov ax, [bx]
    Обратите внимание, что разница между this и Register
    При адресации используются квадратные скобки ([]), которые различают
    нормальный регистр из области памяти.
  5. Базовая относительная адресация
    Возможная комбинация методов прямой и косвенной адресации
    может быть, когда указан косвенный адрес, а также смещение
    от этого значения. Чтобы указать базовую относительную адресацию, программист
    должен указывать базовый регистр и смещение / смещение как сумму.

    примеры:
    mov ax, [bx + 4]
    mov ax, [bx] +4
    mov ax, 4 [bx]
    Все эти инструкции будут использовать один и тот же адрес,
    что на 4 больше, чем адрес, хранящийся в регистре bx.В
    разрешены только регистры BX и BP (так называемые «базовые»
    регистры). Этот метод также можно назвать прямой индексированной адресацией,
    когда он использует регистры SI и DI (так называемый «индекс»
    регистры).
  6. Базовая индексированная адресация
    Это комбинация базового регистра и прямой индексированной адресации.
    Вместо того, чтобы указывать адрес, хранящийся в одном регистре,
    ЦП может складывать значения, хранящиеся в двух регистрах, чтобы получить
    эффективный адрес.Конечно, также можно указать смещение.
    Поскольку этот метод использует базовый регистр и индексный регистр,
    есть только четыре допустимых комбинации регистров.
    пример:
    mov ax, [bx + si + 4]
    mov ax, [bx] [si] +4
    mov ax, [bx] [si + 4]
    Все эти инструкции будут использовать один и тот же адрес.
    Если BX = 4 и SI = 8, то адрес будет 4 + 8 + 4 = 16 (десятичный).
    = 10ч.
    Базовая индексированная адресация полезна для двумерного индексирования.
    массивы, тогда как другие методы используются для более простых одномерных
    случаи.
Полная таблица режимов адресации
только смещение
BX + смещение
BP + смещение
SI + смещение
DI + смещение
BX + SI + смещение
BX + DI + смещение
БП + СИ + рабочий объем
BP + DI + рабочий объем
Пример использования нетривиальных режимов адресации

Индексные регистры могут использоваться так же, как индексы массивов в высокоуровневых
языков.Предположим, что массив байтов хранится в памяти по адресу
расположение anArray. Затем, чтобы получить доступ ко второму элементу, нам понадобится
использовать адрес anArray + 1. Чтобы получить доступ ко второму элементу, мы
используйте anArray + 2 … и т. д. Чтобы получить доступ к произвольному элементу
мы можем использовать переменный индекс, например. BX. Таким образом, нам нужно проиндексировать массив anArray + BX.
Написано на ассемблере, это переводится в anArray [BX] или [anArray + BX]
или [BX] + anArray.

Сегмент перекрытия

Вместо того, чтобы все ссылки на память берутся из ДАННЫХ
сегмент, программист может явно сказать ассемблеру прочитать
место в памяти, использующее другой сегмент.Для этого имя
сегмента сегмента должен быть добавлен к адресу «:».

пример: mov ax, например: [bx]

Это вычисляет эффективный адрес, используя вместо этого регистр ES.
регистра DS, который обычно используется. Аналогично регистр CS
при необходимости может быть изменен. Обычно это используется, когда
Требуется доступ к ячейке памяти в другом сегменте. Скорее
чем изменять регистр DS без необходимости, ES может использоваться для
эта цель.

Стек

8086 использует простой стек в памяти для хранения временных
данные. Он также использует этот стек для хранения адресов возврата, когда
он входит в новую процедуру. Все значения в стеке 16-битные
слова. Регистры, которые управляют стеком, — это SS, SP и BP.

  • SS обозначает сегмент стека
  • SP (указатель стека) указывает на последний элемент наверху
    куча
  • BP (базовый указатель) указывает на нижнюю часть стека.Этот
    используется для настройки и управления информацией в стеке во время
    вызов процедуры.

Стек растет вниз во время обычной работы. Это означает
что при добавлении большего количества элементов в верхнюю часть стека
значение SP уменьшается. Когда стек настроен, SP указывает на
наибольшее значение в стеке. Для примера кода в начале
главы (.STACK 4096), SP будет установлен на 4094
в начале программы — 4094 это два байта от конца
стека, который находится в позиции 4095, так как все сегменты начинаются
в местоположении 0.

Есть несколько команд, которые позволяют программисту сохранять и
получить значения из стека.

Инструкции PUSH / POP

формат:
Источник PUSH
Пункт назначения POP

Возможные операнды следующие:

источник пример
регистр толкающий топор
топор
память push es: [bx]
pop es: [bx]

PUSH уменьшает регистр SP (на 2) и копирует значение в
вершина стека.POP извлекает значение из верхней части
стек и сохраняет его в место назначения, а затем увеличивает
регистр ИП (на 2).

PUSH и POP могут использоваться для сохранения и восстановления значений регистров.
когда регистр необходимо временно использовать для какой-либо другой функции.

Например:
нажмите топор
мов ах, 09ч
mov dx, OFFSET aMessage
int 21h
топор

Здесь значение AX, вероятно, имеет решающее значение, но AX необходимо использовать
для вывода сообщения.Таким образом, его содержимое сохраняется на
стек и восстанавливается после вызова процедуры прерывания.

Инструкция LEA

формат:
регистр LEA, память

Загрузить эффективный адрес загружает указанный регистр со смещением.
ячейки памяти.

Следующие две строки кода идентичны:

mov ax, OFFSET a Сообщение
Lea Axe, Сообщение

Однако инструкция MOV не может быть проиндексирована, потому что OFFSET
это директива ассемблера, а не инструкция.Было бы невозможно
сказать

mov ax, OFFSET aMessage + [BX]

поскольку расчет смещения выполняется во время сборки. На
с другой стороны, можно подать команду

lea ax, aMessage [BX]

пример:

lea dx, a Сообщение
мов ах, 09ч
int 21h

Обратите внимание, что это тот же стандартный метод вывода строки
к экрану.В таких случаях предпочтительно использовать инструкцию LEA.
ситуаций, упрощающих смещение строки в будущем.

Флаги

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

  • для обозначения ошибок
  • для обозначения знака последнего вычисления
  • для включения переноса во время арифметических операций
  • для отладки

Ряд инструкций выполняет определенные задачи в зависимости от текущего
состояние флагов.Наиболее часто используемые флаги:

CF флаг переноса
ZF нулевой флаг
SF знак флаг
OF Флаг переполнения
IF Флаг разрешения прерывания
DF указатель направления
Инструкции PUSHF / POPF

формат:
PUSHF
POPF

Эти инструкции сохраняют и восстанавливают все флаги в / из
куча.Это сохраняет флаги, когда код будет запущен.
собирается изменить важные флаги.

например:
pushf
позвони bigadd; вызов процедуры
popf

Директивы внешнего / общедоступного ассемблера

Чтобы включить процедуры, написанные в отдельных файлах,
программисту необходимо включить директивы PUBLIC в процедуру
file и директивы EXTRN в основном файле программы. Вторичный
файл процедуры не обязательно должен иметь точку входа в программу, поскольку он не может
использоваться самостоятельно.Предполагая, что процедура с именем readsint
был определен для чтения целого числа в регистр AX. Этот
затем процедура экспортируется путем включения следующей строки в
верхняя часть кода во вторичном файле:

PUBLIC readsint

Точно так же программа, которая будет использовать эту процедуру, должна
включить оператор, сообщающий ассемблеру, что он собирается
используйте процедуру из внешнего файла. Для этого следующие
строка должна быть включена в верхнюю часть основного исходного файла программы:

EXTRN readsint: proc

Несколько объявлений можно разделять запятыми
например. EXTRN readsint: proc, writeint: proc

После того, как оба файла будут собраны отдельно, файлы необходимо связать
вместе с такой командой, как:

TLINK ГЛАВНЫЙ.OBJ ВТОРОЙ.OBJ

Библиотека IOASM предоставляет следующие две процедуры, которые могут
быть встроенными в ваши программы, чтобы упростить ввод и вывод:

readsint — считывает целое число в регистр AX

writeint — записывает значение регистра AX в стандартный вывод

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

EXTRN readsint: proc, writeint: proc

а затем свяжите программу с помощью команды TLINK YOURFILE IOASM.LIB

Пример программы № 4

; простая программа числового вывода

ДОССЕГ
.МОДЕЛЬ МАЛЫЙ
.STACK 4096

. ДАННЫЕ
Number1 dw 12
Number2 dw 24
Number3 dw 36

.КОД

EXTRN пишет int: proc
; используйте процедуру из IOASM для вывода числа на экран

Начало программы:
mov ax, SEG _DATA
mov ds, топор

mov bx, Number1; проиллюстрировать инструкцию MOV

мов топор, bx
вызов writeint; вызов процедуры для вывода номера

mov ax, Number2; проиллюстрировать инструкции PUSH / POP

mov bx, топор
толкать bx
мов топор, 0
вызов writeint; вызов процедуры для вывода числа
топор
вызов writeint; вызов процедуры для вывода номера

мов ах, 4 канала
int 21h
КОНЕЦ ПРОГРАММЫ Старт

Эта программа просто загружает ячейку памяти в регистр AX.
и выводит его с помощью процедуры, встроенной в библиотеку IOASM.Вторая часть программы загружает значение в регистр AX
а затем помещает это значение в стек. Регистр AX
очищается, и значение извлекается из стека. Дело в этом
упражнение состоит в том, чтобы доказать, что стек действительно сохраняет значение
положить на это.

Инструкция ADD

формат:
ДОБАВИТЬ назначение, источник

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

пункт назначения источник пример
регистр немедленно добавить топор, 10
регистр регистр добавить ax, bx
регистр память добавить ax, es: [bx]
память регистр добавить es: [bx], ax

ДОБАВИТЬ добавляет содержимое источника в место назначения.Источник
и пункт назначения могут быть байтами или словами, но оба операнда
должны быть одного типа, иначе ассемблер выдаст ошибку.
Если сумма двух чисел не помещается в место назначения,
требуется дополнительный бит, о чем сообщает операция ADD
установка флажков переноса (CF) на 1. Если сумма подходит без утечки,
CF = 0. На другие регистры могут влиять операции сложения, как
хорошо; ZF = 0, если сумма равна нулю, SF = 1, если сумма отрицательная, и т. Д.
Логика основной команды сложения такова:

пункт назначения = пункт назначения + источник

В случае, когда в расчет вводится бит переноса
из предыдущего расчета необходимо использовать инструкцию ADC
вместо ADD, и логика такова:

пункт назначения = пункт назначения + источник + перенос

Инструкция SUB

формат:
SUB назначение, источник

Допустимые комбинации операндов такие же, как и для сложения.

Вычитает исходное значение из целевого. Операция
почти идентично сложению, за исключением того, что используется флаг CF
в качестве займа в случае инструкции SBB (вычесть с заимствованием).
Логика инструкции SUB такова:

пункт назначения = пункт назначения — источник

а логика инструкции SBB такова:

пункт назначения = пункт назначения — источник — перенос

Инструкции INC / DEC

формат:
INC назначение
DEC пункт назначения

INC увеличивает источник на единицу.Вместо того, чтобы использовать ADD для увеличения
регистр или ячейка памяти, инструкция INC выполняет свою работу
быстрее и берет только параметр. Аналогичным образом DEC уменьшает
источник по одному. Это пример многих инструкций, которые
можно заменить последовательностью других инструкций; они есть
используется для ускорения обычных операций.

Инструкция MUL

формат:
Источник MUL

MUL умножает источник на аккумулятор.Если источник
это байтовый регистр или место в памяти, другой элемент, используемый в
умножение — это регистр AL — затем продукт сохраняется
в регистре AX. Если источником является 16-битное слово, регистр AX
автоматически используется как второй параметр, а продукт
хранится в паре регистров DX: AX. Это означает, что регистр DX
содержит высокую часть, а регистр AX содержит низкую часть
32-битное число.

Инструкция DIV

формат:
DIV исходный

DIV делит аккумулятор на источник (который используется как
делитель).Если делитель является байтовым регистром ячейки памяти,
регистр AX используется как делимое, а частное сохраняется
в регистре AL — остаток хранится в регистре AH.
Если делителем является слово, используется 32-битная регистровая пара DX: AX.
поскольку делимое и частное хранятся в регистре AX
— остаток сохраняется в регистре DX.

Инструкцию DIV необходимо использовать очень осторожно из-за
потенциальные риски деления на ноль.3 + 4) мод.

ДОССЕГ
.МОДЕЛЬ МАЛЫЙ
.STACK 4096

. ДАННЫЕ

Number1 dw?
Number2 dw?
Четыре dw 4; постоянная
crlf db 13,10, ‘$’; возврат каретки / перевод строки

.КОД

EXTRN readsint: proc, writeint: proc

; используйте процедуру из IOASM для вывода числа на экран

Начало программы:
mov ax, SEG _DATA
mov ds, топор

вызов readsint; читать в X
mov Number1, ax
Lea DX, CRF
мов ах, 09ч
int 21h

вызов readsint; читать в Y
mov Number2, ax
Lea DX, CRF
мов ах, 09ч
int 21h

mov ax, Number1;
х
mov dx, 0
mul Number1; Х * Х
mul Number1; Х * Х * Х
добавить топор, Четыре; Х * Х * Х + 4
div Number2; (X ^ 3 + 4) div Y

mov ax, dx; остаток на выходе
позвонить по телефону
Lea DX, CRF
мов ах, 09ч
int 21h

мов ах, 4 канала
int 21h
КОНЕЦ ПРОГРАММЫ Старт

Эта программа вводит две переменные (X и Y) и вычисляет
значение выражения:

(X ^ 3 + 4) mod Y.

Инструкция И

формат:
И назначение, источник

Допустимые операнды для этой инструкции такие же, как
для инструкции ДОБАВИТЬ.

И выполняет побитовое И для операндов источника и назначения
и сохраняет результат в операнде назначения. Это полезно
для проверки различных битов в конкретном байте / слове.

например:
и топор 0008h

После этой инструкции AX будет больше 0, только если бит
3 установлено.

например:
и топор, FFF7h

В этом случае можно использовать инструкцию AND для очистки бита 3.

Инструкция OR

формат:
ИЛИ назначение, источник

Допустимые операнды такие же, как для ADD. ИЛИ выполняет побитовое
ИЛИ по источнику и месту назначения и сохраняет результат в
пункт назначения.

например:
или ax, 0008h

Это устанавливает бит 3 в регистре AX.

Инструкция XOR

формат:
Место назначения XOR, источник

Выполняет операцию исключающего ИЛИ для источника / назначения и
сохраняет результат в регистре назначения.

Например:
xor ax, bx
xor топор, топор

Второй пример — интересный, более быстрый, чем обычно, метод
очистки реестра.

Инструкции SHR / SLR

формат:
SHR назначение, 1
Назначение SHR, CL
СХЛ назначения, 1
ШЛ назначения, кл

SHR сдвигает место назначения побитово на 1 позицию или
количество позиций определяется текущим значением CL
регистр.SHL сдвигает пункт назначения влево побитово либо на 1 позицию
или количество позиций, определяемое текущим значением
Регистр CL. Вакантные позиции заполняются нулями.

Например:
shr ax, 1
шл топор, 1

В первом примере ax эффективно делится на 2, а во втором примере
эффективно умножает ax на 2. Эти команды быстрее, чем
использование DIV и MUL для арифметики со степенями двойки.

Пример программы № 6

; простая программа численных расчетов

; вычислить (x и y) / 32

ДОССЕГ
.МОДЕЛЬ МАЛАЯ
.STACK 4096

. ДАННЫЕ
x dw?
y dw?
Пять дБ 5; постоянная
crlf db 13,10, ‘$’; возврат каретки / перевод строки

.КОД

EXTRN readsint: proc, writeint: proc

; используйте процедуру из IOASM для вывода числа на экран

Начало программы:
mov ax, SEG _DATA
mov ds, топор

вызов readsint; читать в X
мов х, топор
Lea DX, CRF
мов ах, 09ч
int 21h

вызов readsint; читать в Y
мов у, топор
Lea DX, CRF
мов ах, 09ч
int 21h

mov ax, x;
х
и топор, у; X и Y
mov cl, Пять
shr ax, cl; (X и Y) / 32

звонок, запись int; выходной результат

Lea DX, CRF
мов ах, 09ч
int 21h

мов ах, 4 канала
int 21h
КОНЕЦ ПРОГРАММЫ Старт

Эта программа очень похожа на предыдущий пример.Различия
заключается в том, что вычисляемая формула равна (x и y) / 32. Деление
реализован как SHR, а не как DIV.

Инструкция JMP

формат:
Цель JMP

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

например:

начало:
Lea dx, aMessage
мов ах, 09ч
int 21h
jmp start

Этот фрагмент кода выведет сообщение, а затем вернется назад.
в начало кода и повторите его действие.Фактически, это будет
производят бесконечный цикл с выводом сообщений на экран.

Инструкция CMP

формат:
CMP-адресат, исходный

Сравните числовое значение пункта назначения с исходным
и установите флаги соответствующим образом. Это сравнение проводится в
форма вычитания, чтобы определить, какой из операндов имеет
большее значение. После инструкции CMP, OF, SF, ZF и CF
установить соответствующим образом.Например, если операнды имеют равные значения,
тогда ZF, если установлен.

Затем эти флаги можно интерпретировать с помощью различных условных
На этой основе могут приниматься инструкции и решения JUMP.

Инструкции JUMP

пример формата:
JE target

Команды условного перехода выполнят переход на основе
предыдущей инструкции CMP. Работа флагов может
быть практически прозрачным, если значимые имена используются для
инструкция прыжка.Например, JE прыгнет, если предыдущее сравнение
дали равенство. JNE будет прыгать, если предыдущее сравнение
было неравным. Если прыжок не выполняется, следующая инструкция
выполняется как обычно.

JA переход, если пункт назначения выше источника
JAE переход, если пункт назначения выше или равен источнику
JB переход, если пункт назначения ниже источника
JBE переход, если пункт назначения ниже или равен источнику
JE переход, если пункт назначения равен источнику
JNE переход, если пункт назначения не равен источнику

Есть еще много инструкций по прыжкам, но в большинстве ситуаций
быть охваченными указанными выше.

Команды условного перехода могут переходить только в то место, которое
физически находится в пределах 128 байт от точки перехода
происходит. Это означает, что прыжок должен быть на ближайший
расположение. JMP не имеет таких ограничений, поэтому две инструкции могут быть
используется в тандеме для написания эффективных алгоритмов принятия решений на
ассемблер.

Например:
cmp ax, bx
Je thenpart
elsepart:
mov cx, 2
jmp endpart
затем часть:
mov cx, 1
конечная часть:

Это эквивалентно следующей строке паскаль-кода:

if (ax = bx) then cx: = 1 else cx: = 2;

Это можно расширить, чтобы смоделировать эквивалент паскальского «дела».
утверждение.

Процедуры

Процедуры на языке ассемблера объявляются с директивой PROC в начале и директивой ENDP в конце.

пример:
TestProc PROC
мов топор, 0
ret
TestProc ENDP

Все процедуры имеют в конце инструкцию RET. Это восстанавливает контроль до точки, после которой процедура была вызвана в основном теле программы.

После объявления процедуры ее можно вызвать с помощью инструкции CALL.

пример:
CALL TestProc

Инструкция CALL сохраняет адрес следующей инструкции в стеке, а затем изменяет IP, чтобы отразить значение ее параметра. Поскольку IP отслеживает текущую выполняемую инструкцию, это изменение заставляет программу переходить к началу процедуры. Когда встречается инструкция RET, она выталкивает старое значение IP из стека, тем самым заставляя процедуру возвращаться к основному телу программы.

Команда CALL также может принимать в качестве параметра регистр или ячейку памяти. В этой ситуации регистр / ячейка памяти содержит адрес вызываемой процедуры.

Модификаторы типа FAR / NEAR

В некоторых случаях процедуры определяются в сегментах, отличных от того, из которого процедура вызывается. В этом случае простого сохранения IP будет недостаточно для запоминания точки вызова; сегмент также должен быть сохранен.Определение процедуры должно быть изменено, чтобы отразить, что это процедура FAR (что означает: не в том же сегменте).

TestProc PROC FAR
мов топор, 0
ret
TestProc ENDP

Чтобы вызвать процедуру, которая, как известно, находится в другом сегменте, оператор CALL также может быть изменен с помощью модификатора типа FAR.
пример:
CALL far ptr TestProc

Иногда помогает явно определить процедуру как NEAR (что означает: в том же сегменте) для создания программ меньшего размера.Безусловная инструкция JMP также может принимать такие модификаторы типа FAR / NEAR.

Модификаторы типа BYTE / WORD

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

пример:
mov ax, word ptr ES: BX

Пример программы № 7


TestProc PROC
мов топор, 4
ret
TestProc ENDP

ProgramStart:
mov ax, SEG _DATA
mov ds, топор

мов топор, 5
звоните, пишите

позвонить в TestProc
звоните, пишите

Эта программа демонстрирует, как определить и вызвать процедуру.Верхняя и нижняя части программы не включены в список, поскольку они довольно стандартные.

Пример программы № 8


мов топор, 11ч

jmp Label3

Этикетка1:
cmp ax, 11ч
jne TheEnd
мов топор, 1
jmp TheEnd

Этикетка2:
мов топор, 2
jmp TheEnd

Этикетка3:
cmp ax, 10ч
je Label2
jmp Label1
мов топор, 3

Конец:
звоните, пишите

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

Инструкция LOOP

формат:
LOOP target

Аналогичной инструкции for в паскале является инструкция LOOP на ассемблере. Инструкция LOOP запускает цикл для предварительно заданного количества итераций. Регистр CX используется для хранения количества итераций. Каждый раз, когда встречается инструкция LOOP, она уменьшает CX и проверяет, достигла ли она нуля. Если да, то управление переходит к следующей инструкции; в противном случае выполняется прыжок в указанную точку.

пример (для):

mov cx, 10
хор топор, топор
addstart:
добавить топор, cx
цикл addstart:

Этот простой цикл находит сумму первых десяти чисел с помощью итеративного метода. Для того, чтобы писать циклы, аналогичные операторам Pascal «while» или «repeat», рекомендуется использовать инструкцию CMP вместе с соответствующим условным переходом.

пример (повтор):

mov cx, 10
хор топор, топор
addstart:
добавить топор, cx
дек cx
cmp cx, 0
jne addstart

пример (в то время как):

mov cx, 10
хор топор, топор
addstart:
cmp cx, 0
je добавление:
добавить топор, cx
дек cx
jmp addstart
добавление:

Инструкция INT

формат:
INT число

INT вызывает соответствующую процедуру прерывания, как указано параметром num.Любое программное прерывание DOS или BIOS может быть вызвано с помощью инструкции INT. Иногда может потребоваться установить определенные значения в определенные регистры для передачи параметров в процедуру обработки прерывания.

пример:
INT 21h

21h — наиболее распространенное прерывание, поскольку оно обеспечивает услуги ввода и вывода для программ DOS.

С точки зрения ассемблера, структура процедуры прерывания аналогична любой нормальной процедуре.Однако при выполнении INT 8086 выполняет ряд дополнительных задач, таких как сохранение флагов. Таким образом, вместо инструкции RET в конце процедуры каждая процедура прерывания имеет в конце инструкцию IRET. Этот IRET выполняет необходимую дополнительную обработку (а именно, восстанавливает флаги) перед возвратом управления вызывающей программе.

Инструкция NOP

NOP (No OPeration) не принимает параметров и ничего не делает. Обычно он используется только для отладки.

Инструкции MOVSB ​​/ MOVSW

пример формата:

РЭП МОВСБ
REP MOVSW

Эти инструкции используются для копирования блока байтов / слов из одного места в памяти в другое. На источник указывает DS: SI, а на пункт назначения указывает ES: DI. Эти пары регистров — единственные, которые можно использовать со строковыми инструкциями, такими как MOVSB ​​/ MOVSW.

Префикс REP используется вместе с реальной инструкцией для повторения операции MOV на протяжении всего блока; она похожа на инструкцию LOOP.Чтобы использовать REP, CX сначала должен быть установлен на количество элементов (байтов / слов) в блоке.

например:

mov cx, 100
Леа си, Источник
lea di, es: Dest
cld
rep movsb

CLD сбрасывает флаг направления. Это сообщает 8086, что он должен увеличивать регистры SI и DI после каждой итерации. Если флаг направления установлен вместо сброса (с использованием STD), то 8086 будет уменьшать SI и DI после каждой итерации.Это создает эффект обратного копирования блока.

Прочие инструкции
CMPS сравнивает две строки в памяти
SCAS сканирует строку в поисках элемента
LODS загружает элемент из строки
СТОС сохраняет элемент в строку
IN вводит значение с аппаратного порта
ВЫХ выводит значение на аппаратный порт
AAA ascii настроить для добавления
AAD ascii скорректировать для деления
AAM ascii с поправкой на умножение
AAS ascii с поправкой на вычитание
CBW преобразовать байт в слово
CWB преобразовать слово в двойное слово
DAA десятичная корректировка для сложения
DAS десятичная корректировка для вычитания
CLC флаг сброса
СТК установить флаг переноса
CLI очистить флаг прерывания
STI установить флаг прерывания
CMC дополнительный флаг для переноски
DAA десятичная корректировка для сложения
DAS десятичная корректировка для вычитания
JCXZ прыжок, если CX равен нулю
JG скачок, если больше
JGE прыжок, если больше или равно
JL прыжок, если меньше
JLE прыжок, если меньше или равно

Указатель нагрузки

Указатель нагрузки

LDS с использованием DS
LES с использованием ES
ПЕТЛЯ петля при этом равна
ПЕТЛЯ цикл пока не равен
NEG отрицать
НЕ логическое НЕ
ROL повернуть влево
ROR повернуть вправо
REPE повторять, пока равно
REPNE повторяется, но не равно
XCHG обмен двумя операндами
XLAT перевести с помощью таблицы

Сборка x86 Зачем использовать Push / Pop вместо Mov?

Да, обычно вы всегда должны использовать mov ecx, 9 по соображениям производительности. Он работает более эффективно, чем push / pop`, как однократная инструкция, которая может выполняться на любом порту. (Это верно для всех существующих процессоров, протестированных Агнером Фогом: https://agner.org/optimize/)


Обычная причина для push imm8 / pop r32 заключается в том, что машинный код не содержит нулевых байтов . Это важно для шелл-кода , который должен переполнять буфер с помощью strcpy или любого другого метода, который обрабатывает его как часть строки C неявной длины, заканчивающейся байтом 0 .

mov ecx, немедленный доступен только с 32-битным немедленным запуском, поэтому машинный код будет иметь вид B9 09 00 00 00 . по сравнению с 6a 09 нажмите 9; 59 pop ecx.

(ECX — номер регистра 1 , откуда берутся B9 и 59 : младшие 3 бита команды = 001 )


Другой вариант использования — это чисто размер кода : mov r32, imm32 — 5 байтов (без использования кодирования ModRM, которое помещает номер регистра в младшие 3 бита кода операции), потому что x86, к сожалению, не имеет расширенный знаком опкод imm8 для mov (нет mov r / m32, imm8 ).Это существует почти для всех инструкций ALU, восходящих к 8086.

В 16-битном 8086 эта кодировка не сэкономила бы места: 3-байтовая короткая форма mov r16, imm16 была бы так же хороша, как гипотетическая mov r / m16, imm8 почти для всего, за исключением немедленного перемещения в память, где требуется форма mov r / m16, imm16 (с байтом ModRM).

Поскольку в 32-битном режиме 386 не добавлялись новые коды операций, а просто изменялись размер операнда по умолчанию и непосредственная ширина, эта «пропущенная оптимизация» в ISA в 32-битном режиме началась с 386.Поскольку полная ширина имеет длину 2 байта, добавление r32, imm32 теперь длиннее, чем add r / m32, imm8 . См. Сборку x86 16-битное против 8-битного непосредственного кодирования операнда. Но у нас нет этой опции для mov , потому что нет кода операции MOV, который бы подписал (или расширил с нуля) его немедленное.

Интересный факт: clang -Oz (оптимизация по размеру даже за счет скорости) компилирует int foo () {return 9;} в push 9 ; поп-ракс .

См. Также Советы по игре в гольф в машинном коде x86 / x64 на Codegolf.SE (сайт об оптимизации размера обычно для развлечения, а не для размещения кода в небольшом ПЗУ или загрузочном секторе. Но для машинного кода оптимизация размера имеет практическое применение иногда даже в ущерб производительности.)

Если у вас уже был другой регистр с известным содержимым, создание 9 в другом регистре может быть выполнено с помощью 3-байтового lea ecx, [eax-0 + 9] (если EAX содержит 0 ).Просто код операции + ModRM + disp8. Таким образом, вы можете избежать взлома push / pop, если вы уже собирались обнулить любой другой регистр. lea едва ли менее эффективна, чем mov , и вы можете учитывать это при оптимизации скорости, потому что меньший размер кода имеет незначительные преимущества скорости в больших масштабах: кэш L1i попадает и иногда декодируется, если кеш uop еще не горячий.

Введение в сборку

Введение в сборку

Напомним, что когда мы пишем код C ++, компилятор преобразует его в исполняемый машинный код, который фактически выполняется на аппаратном обеспечении ЦП.Машинный код построчно эквивалентен языку ассемблера.

Один из способов начать изучение языка ассемблера — использовать «Дизассемблер», чтобы увидеть, что компилятор генерирует из вашего кода. (В NetRun выберите Параметры -> Действия -> Разобрать, затем запустить.) Например, с учетом этого кода C ++:

 long foo () {
возврат 7;
} 

(Попробуйте сейчас в NetRun!)

Мы можем скомпилировать это, используя: g ++ code.c -c -fomit-frame-pointer

Затем мы можем разобрать его с помощью: objdump -drC -M Intel code.o

 code.obj: формат файла elf64-x86-64


Разборка раздела .text:

0000000000000000 :
   0: b8 07 00 00 00 mov eax, 0x7
   5: c3 ret
 

Текст справа, начинающийся с «mov» и «ret», является языком ассемблера.

Мы можем взять этот код языка ассемблера, преобразовать его в машинный код с помощью «ассемблера», такого как nasm, и запустить его!

 mov eax, 7
Ret
 

(Попробуйте сейчас в NetRun!)

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

Инструкции

Assembly — очень странный язык, разработанный в основном для машины, на которой он работает, а не для программиста. Например, «mov» и «ret» — это инструкций для выполнения ЦП. Вы не можете добавлять новые инструкции без изменения ЦП; например, Intel добавила инструкцию «aesenc» (шифрование AES) в 2010 году. За прошедшие годы были добавлены сотни инструкций, но некоторые из наиболее часто используемых инструкций:

  • mov, переместить данные.
  • ret, возврат из функции.
  • доп., Доп.
  • суб, вычитание.
  • imul, умножить.
  • jmp, выполнить код в другом месте.
  • cmp, сравните значения.
  • jlt, прыгать, если меньше.

Мы будем работать над этими инструкциями на этой неделе!

Регистры

В ассемблере у вас нет переменных, но вы оперируете данными в регистрах . Регистр на самом деле представляет собой крошечный элемент памяти внутри ЦП с фиксированным размером.Когда ЦП выполняет строку типа «mov eax, 7», он сохраняет константу 7 в регистре eax, который имеет ширину 32 бита, ширину «int» в C или C ++. Подобно тому, как большинство программ на C ++ тратят свое время на перетасовку значений между переменными, большинство ассемблерных программ тратят свое время на перетасовку значений между регистрами.

Вот некоторые из наиболее удобных и простых в использовании 32-битных регистров, и кто их использует. (Мы также рассмотрим другие размеры и типы регистров.)

Банкноты 32-бит
Значения возвращаются функциями в этом регистре.Инструкции умножения также помещают сюда младшие биты результата. eax
Скретч-регистр. Некоторые инструкции используют его как счетчик (например, SAL или REP). ecx
Скретч-регистр. Команды умножения помещают сюда старшие биты результата. edx
Скретч-регистр. Аргумент функции №1 в 64-битном Linux. edi
Скретч-регистр. Также используется для передачи аргумента функции №2 в 64-битном Linux. ESI

Большая проблема с регистрами в том, что они * аппаратные *: вы застряли с существующими именами и размерами, и каждая функция должна разделять их, как глобальные переменные. Если бы вы придумали новый язык, в котором всего пять глобальных переменных со странными жестко запрограммированными именами, вас бы посмеяли прямо в отдел кадров, чтобы вас уволили!

Одно предостережение: если вы видите некоторую сборку, в которой перед именами регистров стоит знак процента, например «% eax», вы, вероятно, смотрите на синтаксис GNU / AT&T, который досадно размещает все регистры в обратном порядке. из синтаксиса Intel, который мы будем использовать.

Форматирование

В отличие от C / C ++, язык ассемблера не чувствителен к регистру. Это означает, что «mov eax, 7» и «mOv EaX, 7» эквивалентны.

Точка с запятой обозначает начало комментария. В отличие от C / C ++ / Java / C # / …, точка с запятой НЕОБЯЗАТЕЛЬНА в сборке! Я обычно оставляю точки с запятой для строк без комментариев, потому что в противном случае я испытываю соблазн сделать это:

 mov ecx, 5; mov eax, 3; Ой! 

Не похоже, но точка с запятой делает эту вторую инструкцию КОММЕНТАРИЕМ!

В отличие от C / C ++, сборка ориентирована на строки, поэтому вам нужна новая строка после каждой строки сборки, и следующее НЕ БУДЕТ РАБОТАТЬ:

 mov eax, 
5

Линейно-ориентированные вещи действительно раздражают.Будьте осторожны, чтобы ваш редактор по ошибке не добавил новые строки в длинные строки текста!

Арифметические операции в сборке

Вот как складываются два числа в сборке:

  • Поместите первое число в регистр
  • Запишите второе число в регистр
  • Сложите два регистра
  • Вернуть результат

Вот эквивалент C / C ++:

 int a = 3; 
интервал c = 7;
а + = с;
return a;

И, наконец, код сборки:

 mov eax, 3 
mov ecx, 7
add eax, ecx
ret

(исполняемая ссылка NetRun)

Вот арифметические инструкции x86. xor eax, ecx не ~ не eax

Будьте осторожны при этом! Сборка ориентирована на * строку *, поэтому ничего подобного нельзя сказать:
add edx, (sub eax, ecx); не будет работать

добавить edx, eax-ecx; не будет работать

, но вы можете сказать:
sub eax, ecx
add edx, eax

В ассемблере арифметика должна быть разбита на одну операцию за раз, по одной инструкции на строку!

Обратите внимание, что idiv действительно странно, даже по стандартам языка ассемблера.»idiv bot» делит eax на bot (часть eax жестко запрограммирована). Но он также обрабатывает edx как старшие биты выше eax, поэтому вы должны сначала установить их в ноль.
idiv bot
означает:
top = eax + (edx << 32)
eax = top / bot
edx = top% bot

Вот пример:

 mov eax, 73; верхние 
mov ecx, 10; нижний
mov edx, 0; старшие биты top
idiv ecx; разделить eax на ecx
; теперь eax = 73/10, edx = 73% 10

(Попробуйте сейчас в NetRun!)

Какая странная инструкция!


CS 301 Lecture Note, 2014, Dr.Орион Лоулор, Департамент компьютерных наук UAF.

Assembly — OSDev Wiki

До самого ядра компьютеров каждый сигнал, посылаемый в любую часть машины, представляет собой серию электрических импульсов. Если электрический импульс высокий, например 5 вольт, это означает, что отправляется двоичная 1 цифра. Если электрический импульс низкий, например 0 вольт, это означает, что отправляется двоичная цифра 0. Сложите 4 двоичных цифры вместе, и вы получите кусочек. Складываем 8, получаем байт. Сложите 16 вместе, вы получите слово.Компьютеры понимают команды в таких сегментах, но люди этого не хотят. Таким образом, они разработали лучший способ просмотра такой информации, и это было в шестнадцатеричном формате. Во-первых, для отображения одной команды не требовалось 16 символов и в целом стало удобнее запоминать такие команды, как:

 90 - Нет работы
CC - перерыв
E9 - 16-битный переход
CD - прерывание
и т.п.
 

Но вскоре стало понятно, что такие коды должны быть удобными для пользователя, поэтому они изобрели ассемблерный код. С помощью ассемблерного кода вы можете представлять операции с помощью специальных кодов, которые более удобочитаемы.Например, произошел скачок JMP, прерывание INT, NOP не выполнял никаких действий и т. Д.

Ассемблер — это программа, которая читает файл сборки (обычно с расширением .asm) и преобразует его в двоичный исполняемый файл. Это очень похоже на компилятор, генерирующий исполняемую программу. Однако ассемблеры рассматриваются отдельно от компиляторов, потому что компиляторы скрывают низкоуровневые детали вашей системы, в то время как ассемблеры раскрывают вам эти детали.

Исходный код, написанный для любого ассемблера, определяется как «язык ассемблера.«Но это мало что значит, поскольку каждому ассемблеру может потребоваться разный исходный код для одной и той же программы на абсолютно идентичной платформе.

Синтаксис сборки

В сборке за каждой инструкцией (или операцией / оператором / кодом операции) следует список параметров (или операндов), разделенных запятыми. Эти операнды могут быть либо непосредственными значениями (например, числами, такими как 1, 2, 0x16, 0101b), либо регистрами, либо ячейкой памяти. Например,

Инструкция mov, операнды — eax и 123.Здесь eax — регистр, а 123 — непосредственное значение.

Существует два основных синтаксиса сборки: синтаксис Intel и синтаксис AT&T.

Синтаксис Intel

Вообще говоря, первый операнд операции является операндом-адресатом, а другой операнд (или операнды) является исходным операндом. Например:

Команда mov берет значение из исходного операнда и помещает его в целевой операнд, так что значение 123 будет помещено в регистр eax.Первоначальный синтаксис Intel — это ассемблер Intel ASM386 (позже переданный RadiSys по лицензии, но в конечном итоге умер). С тех пор многие сборщики изобрели множество его вариаций. Кроме того, он используется в большинстве онлайн-руководств по сборке, потому что большинство этих руководств было написано, когда TASM (который использует синтаксис Intel) был доминирующим ассемблером.

Синтаксис AT&T

Синтаксис AT&T является обратным синтаксису Intel. Данные, над которыми работают, перемещаются слева направо.Вот тот же оператор, что и выше, в синтаксисе AT&T.

Есть и другие отличия от операндов:

  • Литеральные значения, такие как 123, начинаются с префикса «$» (см. Пример выше).
  • Ячейки памяти не имеют префикса: mov 123,% eax перемещает значение, хранящееся в 123, в eax.
  • Регистры имеют префикс ‘%’
  • При использовании регистра в качестве указателя он помещается в круглые скобки: mov (% eax),% ebx перемещает значение, сохраненное в указателе, хранящемся в eax, в ebx.
  • При использовании оператора с ячейками памяти (без регистра) после инструкции необходим суффикс. Например: movl $ 123, 123. ‘l’ (обозначает длинное) и требуется, чтобы сообщить ассемблеру, что значения 32-битные.
  • Указатели могут быть смещены константами путем добавления к ним префикса: 4 (% eax) указывает на 4 +% eax.
  • Указатели могут быть смещены другим регистром, записав их как: (% eax,% ebx), что указывает на% eax +% ebx
  • Наконец, вы можете объединить все это и добавить шкалу, если хотите: 4 (% eax,% ebx, 2) будет 4 +% eax +% ebx * 2

См. Также

Статьи

Внешние ссылки

  • GNU Binutils — Коллекция бесплатного программного обеспечения, включая ассемблер GNU (газ)
  • gas manual — Официальное руководство для GNU ассемблера (газ)
  • NASM — Официальный сайт Netwide Assembler: NASM
  • x86_Assembly — Викибук по написанию ассемблера для компьютеров на базе x86
  • Искусство программирования на ассемблере, электронная книга, 16-битное издание для DOS.(Более поздние выпуски сосредоточены на диалекте ассемблера высокого уровня, созданном автором.)
  • PC ASM, онлайн-книга, сборка 32-битного защищенного режима на x86
  • x86 Opcode and Instruction Reference, база данных инструкций и кодов операций в виде бесплатных просматриваемых, доступных для поиска и загружаемых файлов XML. Они также продают печатную версию.

Ассемблер / язык ассемблера

Ассемблер / язык ассемблера

Что действительно нужно всем, так это хорошее и подробное знакомство с ассемблером.
который объясняет основы, прежде чем углубляться в техническую информацию.Один хороший
онлайн-руководство такого рода, которое я нашел, находится здесь:

http://udgftp.cencar.udg.mx/ingles/tutor/Assembler.html

Также проверьте сборку x86
Language Webring, который, как можно догадаться по названию, является веб-кольцом
о языке ассемблера для чипов x86, который относится к серии Intel x86,
286, 386, 486 и т.д. Пентиумы также включены.

И проверить
http://www.riverland.net.au/~mdunn для нескольких примеров изящных трюков с asm,
например, как управлять динамиком и т. д. (Здесь я научился делать
TSR.)

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

Вывести шестнадцатеричное число в виде удобочитаемого текста

MOV AL, 050h; Измените это на то, что вам нравится, для тестирования.

; Преобразуйте значение в AL в двузначное шестнадцатеричное число и выведите
; результат на экран.
; Сначала мы используем левый символ ASCII, но его преобразование уничтожает
; содержимое AX...
PUSH AX; ... так что давайте сохраним AX, чтобы мы могли восстановить его позже.
SHR AL, 4; AL теперь содержит старший полубайт исходного числа.
ADD AL, 30h; Если этот полубайт был от 0 до 9, он теперь преобразован в ASCII.
CMP AL, 39h; Символ 9 или меньше?
JLE PRINTFIRSTHEXCHAR; Если да, то он готов к печати.
ADD AL, 7; в противном случае это A-F, и его нужно увеличить на 7.
; (Между цифрой 9 и заглавной буквой A в
; Таблица ASCII.)
PRINTFIRSTHEXCHAR:
MOV AH, 0Eh; Давайте использовать INT 10, E для вывода AL в виде текста ASCII на экран.MOV BH, 0; Номер страницы для текстовых режимов.
ИНТ 10ч
POP AX; Теперь давайте восстановим AX и проделаем все заново для нижнего полубайта!
AND AL, 0Fh; Обнулить верхний полубайт, оставив только нижний ...
; ... а в остальном то же, что и выше.
ДОБАВИТЬ AL, 30ч
CMP AL, 39ч
JLE PRINTSECONDHEXCHAR
ДОБАВИТЬ AL, 7
PRINTSECONDHEXCHAR:
MOV AH, 0Eh
MOV BH, 0
ИНТ 10ч

MOV AX, 4C00h; Завершить программу
ИНТ 21ч
 

Программа, которая включает курсор мыши

; Включает курсор мыши
MOV AX, 01
ИНТ 33ч
MOV AX, 4C00h
ИНТ 21ч
 

Первая строка этой программы — просто комментарий.В ассемблере
точка с запятой начинается с комментария, а затем комментарий продолжается до
остальная часть этой строки.

Строка MOV AX, 01 помещает значение 01 (другими словами, просто 1)
в AX, который является регистром ЦП. Регистры — это небольшое временное хранилище
области вашего процессора, используемые для обработки данных.

Линия INT 33h вызывает программное прерывание номер 33, которое используется
для функций мыши. (Программные прерывания — это мини-функции, встроенные в
ваш компьютер.) INT 33 получает все свои инструкции от AX.Что бы мы ни
указанный для AX скажет INT 33, что делать. В данном случае мы указали
1, что означает, что мы говорим INT 33 включить курсор мыши. Это
именно то, что он будет делать сейчас.

Последние две строки здесь просто для программы
прекращение. INT 21 получает инструкции от AH, и когда мы передаем 4C в
это говорит ему выключить программу. (Это важно. Если мы не указали
эти последние две строки из этой программы, курсор мыши будет включен,
но программа перестала бы работать и заблокировала компьютер.)

К настоящему времени вы, несомненно, задаетесь вопросом: откуда я знаю, что INT 33 управляет
мышь и что MOV AX, 01 скажет включить курсор? Конечно это
Достаточно легко, если вы это знаете, но где вы можете найти эту информацию?

Ответ очень прост: внешние ссылки. Целые книги были
записывается в INT и регистры, а также в MOV, которые их запускают. Получите книгу от
ваша местная библиотека, которая все объясняет. А еще лучше купить. Если хотите
станьте серьезным программистом, вы будете постоянно к нему обращаться.

Если вы не хотите покупать книгу или вам просто лень идти в
библиотеки, вы также можете легко найти полные списки INT в Интернете. В
Фактически, отсюда вы можете скачать программу под названием HelpPC 2.10, которая
это справочная программа, наполненная полезной информацией для программистов и
опытные пользователи компьютеров.

Скачайте здесь

Вы также можете получить HelpPC от Simtel по следующей ссылке:

ftp://ftp.simtel.net/pub/simtelnet/msdos/info/helppc21.почтовый индекс

Теперь, когда у вас есть программа, которая сообщает вам все, что вам нужно знать, вы
может быть интересно, как именно создавать программы на ассемблере. Маленькая программа
перечисленное выше выглядит, возможно, достаточно интересно, но как его запустить? Только
введите его в командной строке компьютера? Ответ: Нет. Вы должны использовать
компилятор. Так же, как BASIC, C и Java, ассемблер все еще не чистый, сырой
машинный код, поэтому вы должны использовать программу для его перевода, чтобы компьютер
может запустить его.

Если вы используете MS-DOS или Windows 95, у вас уже есть программа, которая может
инструкции по компиляции ассемблера: Debug.Поставляется с MS-DOS и Windows 95.
В командной строке просто введите debug и нажмите ENTER. Ты получишь
дефис для подсказки. (Отладка на удивление неинтуитивна, но компенсирует
это с силой.) Теперь, если вы хотите создать программу, описанную выше, вы можете как
хорошо назовите программу перед тем, как начать. Это можно сделать, просто набрав
n имя_файла где «имя_файла» — это имя, которое вы хотите дать файлу.
(Используйте .COM для расширения, так как это программа.) N — это Debug’s
команда для указания имени файла.Большинство команд отладки — это одна
характер, собственно. (Я сказал, что это не интуитивно.) Теперь вы можете ввести
A и нажмите ENTER. Это команда Debug для «сборки», и вы
теперь можете начать вводить текст в программе, нажимая ENTER в конце каждой строки,
конечно. (Игнорируйте первую строку при вводе, так как это просто
все равно прокомментируйте.) Как только вы закончите, запишите номер в конце
Подсказка. Это число показывает, сколько байтов вы уже ввели. (Минус
100. Обратите внимание, что он начинается со 100, поэтому фактическое количество байтов равно 100.
меньше.) Для этой конкретной программы вы увидите, что она имеет длину 9 байт.
Теперь нажмите ENTER, чтобы выйти из сборки и вернуться к подсказке с дефисом. Один
небольшая особенность Debug заключается в том, что вы должны указать размер в байтах программ, которые вы
напишите, прежде чем сохранять их. Итак, введите RCX (сокращение от Register CX).
Это заставит Debug показать вам текущее значение регистра CX (это будет
0), а подсказка станет двоеточием, указывая на то, что вам нужно ввести
новое значение для CX. Введите 9 и нажмите ENTER (так как мы пишем 9
байтов).Теперь просто введите w (для «записи»), чтобы сохранить программу. Все сделано.
Теперь вы можете ввести q (для выхода), чтобы выйти из отладки.

Теперь программа находится в текущем каталоге. Если вы хотите увидеть свой
курсор мыши, просто запустите программу (убедитесь, что у вас загружен драйвер мыши
первый). Теперь вы должны увидеть его и сможете перемещать с помощью мыши.

Кстати, обратите внимание, что числа INT выше имеют букву H после них. Это
потому что некоторые компиляторы ассемблера предполагают, что если вы просто используете простые числа,
они должны быть преобразованы в их шестнадцатеричные значения.Отладка не работает, но
A86, очень популярный компилятор, делает. По сути, он будет использовать INT 21, где
предполагается использовать 33 (33 десятичное = 21 шестнадцатеричное) и 15, где предполагается
используйте 21 (21 десятичное = 15 шестнадцатеричное). Установка H после проясняет любые проблемы
что могло бы придумать это.

Да, и если вы хотите снова выключить курсор, вот программа, которая
делает именно это:

MOV AX, 02
ИНТ 33ч
MOV AX, 4C00h
ИНТ 21ч
 

Как видите, эта программа практически идентична. Единственная разница в том,
значение 2 в AX указывает INT 33 выключить курсор.

Приостановить компьютер, пока не будет нажата кнопка мыши

MOV AX, 5; 5 в AX сообщает INT 33, что нужно получить информацию о кнопках мыши.
INT 33h; получить информацию о кнопке мыши
CMP AX, 1b; сравнивает AX (состояние кнопки) с 1
JL 0100h; если ни одна кнопка не нажата, вернуться к началу

; AX будет равен нулю, пока ни одна кнопка не нажата. JL означает, что если первый
; операнд меньше второго, прыгайте!

mov ax, 4C00h; завершить программу
int 21h
 

Работа этой программы должна быть достаточно простой и очевидной.

«Привет, мир».

Следующая программа распечатывает тестовое сообщение канонической программы:
«Привет мир.» Практически вся первая программа на BASIC или C — это
программа, которая распечатывает это сообщение. Вот один из ассемблера, который делает
это

MOV AX, SEG СООБЩЕНИЕ
MOV DS, AX
MOV DX, СМЕЩЕНИЕ СООБЩЕНИЯ

; Эти три строки делают DS: DX указателем на сегмент: смещение
;СООБЩЕНИЕ. Обратите внимание, что AX используется в качестве промежуточного звена для сегмента,
; потому что некоторые компиляторы ассемблера не позволяют немедленно выполнить MOV
; значения в сегментные регистры (например, DS) напрямую.Некоторые делают, некоторые нет.
; Во избежание проблем вместо MOV DS, SEG MESSAGE мы делаем
; это в AX, а затем в MOV DS, AX.

mov ah, 0009; 9 в AH заставляет INT 21 печатать строку
int 21h; ссылка в DS: DX.
mov ax, 4C00h; завершить программу
int 21h

СООБЩЕНИЕ: DB 'Hello, world. $'
 

Эта программа также должна быть довольно очевидной. Он вводит некоторые новые
концепции, но просто изучите это, и я уверен, что вы поймете, что все
делает.

Создание текстовой «кнопки» мыши

А теперь еще одна программа для мыши.Вся эта программа полностью
не требует пояснений. Это больше, чем те, что указаны выше, но это
хорошо прокомментированы.

; Дает вам экранную кнопку, по которой можно щелкнуть мышью, и
; продолжается только тогда, когда вы это делаете!
mov ah, 06h; это вызывает SCROLL SCREEN UP
mov al, 00h; AL указывает, сколько строк ... 0 просто очищает экран
mov ch, 00h
mov cl, 00h
mov dh, 24ч
mov dl, 80h
int 10h

; Вышеупомянутые строки просто очищают экран.

mov ah, 02h; Установить позицию курсора
mov dh, 0Bh; строка
mov dl, 22h; столбец
int 10h; Собственно делает это.:)
MOV AX, SEG СООБЩЕНИЕ
MOV DS, AX
MOV DX, СМЕЩЕНИЕ СООБЩЕНИЯ

mov ah, 0009; 9 в AH заставляет INT 21 печатать строку
int 21h; ссылка в DS: DX.

; Итак, мы напечатали КНОПКУ именно там, где хотели !!!

mov ax, 0001
int 33h

; Итак, мышь включена.

МЫШЬ:

MOV AX, 5; 5 в AX сообщает INT 33, чтобы получить информацию о нажатии кнопки мыши
INT 33h; получить информацию о кнопке мыши
CMP AX, 1b; сравнивает AX (состояние кнопки) с 1
JNE MOUSLOOP; если не нажата ТОЛЬКО ЛЕВАЯ кнопка, вернитесь в MOUSLOOP

; (Для этого подойдет только левая кнопка... Не то.)

MOV AX, 3
INT 33h; Теперь мы получаем информацию о ПОЛОЖЕНИИ через INT 33,3!

; CX теперь горизонтальное положение мыши. 0 - 639
; DX теперь вертикальное положение мыши. 0–199

CMP CX, 270
JL MOUSLOOP; Если CX меньше 270 (левый край кнопки), вернуться
CMP CX, 315
JG MOUSLOOP; Если CX больше 400 (правый край), вернуться
CMP DX, 85
JL MOUSLOOP; Если DX меньше 85 (вверху), вернитесь
CMP DX, 95
JG MOUSLOOP; Если DX больше 110 (внизу), вернитесь

; Приведенные выше значения размеров кнопок являются приблизительными, но
; должно быть достаточно хорошо.mov ax, 0002
int 33h

; Это просто скрывает курсор. В противном случае он останется включенным, когда программа
; закончился! :)

mov ax, 4C00h; завершить программу
int 21h

СООБЩЕНИЕ: DB 'BUTTON $'
 

Проверить наличие драйвера мыши

Часто программе нужно просто проверить, не был ли установлен драйвер мыши.
установлен или нет. (Обычно при запуске программы, требующей мыши, поэтому
при необходимости программа может прервать работу с ошибкой «Нет драйвера мыши».)
программа, которая отображает сообщение об ошибке, если драйвер мыши НЕТ, и
тихо завершает работу, если драйвер был обнаружен.

MOV AX, 0000
ИНТ 33ч
CMP AX, 0000
JNE Ender
; Здесь код для выполнения, если драйвер мыши не установлен.
МОВ АГ, 09
MOV DS, сообщение SEG
MOV DX, сообщение OFFSET
ИНТ 21ч

Эндер:

mov ax, 4C00h; завершить программу
int 21h

Сообщение БД "ОШИБКА: драйвер мыши не обнаружен. $"
 

Возврат уровня ошибки в зависимости от наличия драйвера мыши

Простое дополнение к предыдущей программе, возвращающее уровень ошибки.
в зависимости от наличия драйвера мыши: возвращает 0, если драйвер был
обнаружен, или 1, если драйвер не обнаружен.(В DOS уровень ошибки 0 — это
стандартный код для обозначения «ошибок нет»)

; Проверяет драйвер мыши, возвращает уровень ошибки 0, если ошибок нет, возвращает
; errorlevel 1, если ошибка (т.е. драйвер мыши не установлен)
MOV AX, 0000
ИНТ 33ч
CMP AX, 0000
JNE Itsok
; Здесь код для выполнения, если драйвер мыши не установлен.
MOV AL, 01; вернуть уровень ошибки 1 (что указывает на ошибку)
JMP Ender

Ничего страшного:
MOV AL, 00; Вернуть уровень ошибки 0 (нет проблем)

Эндер:

MOV AX, 4C00h; завершить программу
ИНТ 21ч
 

Создание графического курсора мыши

Определенно немного длиннее и сложнее, чем предыдущие программы,
но, на мой взгляд, оно того стоит.

; Включите графический курсор мыши и сделайте его похожим на указывающую руку.

mov ах, 00ч
mov al, 13h
int 10h

; Три вышеперечисленных строки переключаются на 256 VGA!

MOV AX, 09h; Указывает INT 33 установить курсор мыши
MOV BX, 0Ah; горизонтальная горячая точка (от -16 до 16)
MOV CX, 00h; вертикальная горячая точка (от -16 до 16)
MOV ES, SEG Curlook
MOV DX, СМЕЩЕНИЕ Curlook

; ES: DX = указатель на экран и маски курсора (16-байтовый битовый массив)

INT 33h; УСТАНОВИТЬ КУРСОР МЫШИ !!!

MOV AX, 01h
INT 33h; Включи!

mov ax, 4C00h; завершить программу
int 21h

; ПРИМЕЧАНИЕ. Каждое следующее слово определяет одну строку.; В одном ряду 16 пикселей.
; В каждой строке по 2 байта.
; 1 слово - это 2 байта или 16 бит. Каждый бит набора слов
; этот пиксель должен быть включен или выключен.
; (Размер курсора - 16 на 16 пикселей.)
; (Символы «xB» указывают на двоичное число.)

; Маска экрана:
; Если бит равен 1, то будет отображаться фон под этим пикселем.
; Если бит равен 0, то фон под этим пикселем будет скрыт
; курсором, когда он находится над этой областью.
; Второй байт в каждой строке - это 8 крайних левых пикселей. Первый байт
; - крайние правые 8 пикселей.(Не спрашивайте меня, почему они поменялись местами
;как это.)
; (В общем, эти биты должны быть прямо противоположны их
; аналоги курсора. Таким образом, курсор закрывает то, что находится под ним,
; и ничего вокруг курсора не закрывается.)

Curlook DB 11111111xB, 11111001xB
        ДБ 11111111xB, 11110000xB
        ДБ 11111111xB, 11110000xB
        ДБ 11111111xB, 11110000xB
        ДБ 11111111xB, 11110000xB
        ДБ 11111111xB, 11110000xB
        ДБ 00000111xB, 11000000xB
        ДБ 00000011xB, 10000000xB
        ДБ 00000011xB, 10000000xB
        ДБ 00000111xB, 11000000xB
        ДБ 00000111xB, 11000000xB
        ДБ 00001111xB, 11100000xB
        ДБ 00011111xB, 11110000xB
        ДБ 00011111xB, 11111000xB
        ДБ 00011111xB, 11111000xB
        ДБ 00011111xB, 11111000xB

; Уф! :) Вот только маска экрана... А вот и маска курсора!
; (Если бит равен 1, это означает, что пиксель для курсора будет включен.
; Если бит равен 0, это означает, что пиксель для курсора будет выключен.)
; Второй байт в каждой строке - это 8 крайних левых пикселей. Первый байт
; - крайние правые 8 пикселей. (Не спрашивайте меня, почему они поменялись местами
;как это.)

        ДБ 00000000xB, 00000110xB
        ДБ 00000000xB, 00001111xB
        ДБ 00000000xB, 00001111xB
        ДБ 00000000xB, 00001111xB
        ДБ 00000000xB, 00001111xB
        ДБ 00000000xB, 00001111xB
        ДБ 11111000xB, 00111111xB
        ДБ 11111100xB, 01111111xB
        ДБ 11111100xB, 01111111xB
        ДБ 11111000xB, 00111111xB
        ДБ 11111000xB, 00111111xB
        ДБ 11110000xB, 00011111xB
        ДБ 11100000xB, 00001111xB
        ДБ 11100000xB, 00000111xB
        ДБ 11100000xB, 00000111xB
        ДБ 11100000xB, 00000111xB
 

Красный пиксель, который следует за движением мыши

Вот небольшая приятная программа, которая показывает, как сделать графику, которая
реагировать на движение мыши.В этом случае «графика» — это только одиночный
пикселя, но суть здесь в том, чтобы показать, как улавливать движение мыши, а не как
рисовать красивую графику.

Причина, по которой это важно, состоит в том, что, хотя мы видели выше, как
используйте INT 33,9 для создания нашего собственного графического курсора мыши, INT 33,9 не поддерживает
изготовления цветных курсоров мыши; это может сделать только черно-белая мышь
курсоры. Если вы хотите сделать курсор мыши цветным, прерывания нет
функция для этого в стандартном Microsoft-совместимом драйвере мыши для DOS,
это означает, что вместо использования простого вызова прерывания подключаемого модуля для создания вашего
курсор мыши, вы должны проверить местоположение мыши с помощью INT 33,3 (который
возвращает местоположение указателя мыши на экране), а затем нарисуйте свой
собственный курсор мыши там.

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

mov ах, 00ч
mov al, 13h
int 10h
; Три вышеперечисленных строки переключаются в режим 13h, который является 256-цветным VGA.

MAINLOOP:

; Давайте проверим, нажал ли пользователь Q для выхода.
IN AL, 60h; чтение из порта ввода клавиатуры в AL
CMP AL, 10h; клавиша "Q" имеет скан-код 10h
JE QUITTER; Если AL равно 10h, пользователь нажал Q, поэтому перейдите к процедуре выхода

mov ax, 3; Получить позицию мыши (а также статус кнопки, который здесь не используется)
int 33h

; Теперь CX и DX содержат позиции X и Y мыши:
; CX = положение по горизонтали (X) 0 - 639
; DX = вертикальное (Y) положение 0 - 199

; Во-первых, исправим CX, так как он на самом деле вдвое шире, чем нам нужно.; (Наш видеорежим 320x200, но CX имеет разрешение 640 пикселей.)
; Загрузим CX в AX (так как нам нужно использовать AX для деления),
; затем разделите AX на 2 и переместите AX обратно в CX.
MOV AX, CX
MOV BX, 2
; Здесь я хотел бы просто использовать DIV BX, чтобы разделить AX на 2.
; (Мы также могли бы использовать DIV BL для 8-битного деления, но это сломало бы
; все вверх, если пользователь перемещает мышь слишком далеко вправо, так как наш
; экран имеет ширину 320 пикселей, но 8-битный регистр достигает только 256.)
; Однако есть еще одна небольшая деталь, на которую мы должны обратить внимание,
; а именно регистр DX, который, вероятно, вызовет сбой этой программы с
; ошибка переполнения при разделении, если мы не обращаем на нее внимания.; Инструкция DIV BX делит 32-битное значение в DX: AX на BX.
; Если значение DX отличное от 0 или 1, вероятно, вызовет разделение
; переполнение, потому что результаты не помещаются в 16-битный регистр AX, который
; приведет к сбою программы.
; В любом случае, мы хотим разделить только то, что находится в AX, а не в DX,
; поэтому давайте установим DX в 0.
; Однако DX по-прежнему содержит наше важное значение Y для положения мыши, поэтому
; мы сохраняем DX, сначала нажав PUSH, а затем POP, когда закончим с ним.
PUSH DX
MOV DX, 0
DIV BX; НАКОНЕЦ!
POP DX
MOV CX, AX
; CX делится на 2

; Теперь давайте умножим DX на 320, так как каждая строка имеет длину 320 пикселей, поэтому
; размер ячейки памяти необходимо увеличить на 320 для каждой вертикальной строки.MOV AX, 320
MUL DX
; AX теперь содержит DX, умноженное на 320. Расположение по вертикали в видеопамяти
; правильно, поэтому давайте добавим горизонтальное значение, все еще содержащееся в CX ...
ДОБАВИТЬ AX, CX
; ... и сохраните окончательное значение местоположения экрана в BX.
MOV BX, AX

MOV DS, 0A000h; сегмент видео с отображением в память VGA
MOV AL, 40; десятичный 40 красный!
MOV [BX], AL; Записывает красный пиксель в текущее местоположение экрана, сохраненное в BX.

JMP MAINLOOP

ЧЕТВЕРТ:

mov ax, 4c00h; завершить программу
int 21h
 

Используйте LODSB для загрузки данных из буфера памяти в аккумулятор

Эта программа содержит 200-байтовую последовательность цветов пикселей.Когда программа
При запуске этот 200-байтовый блок данных загружается в память. Затем программа загружает
данные из этого буфера по одному байту через LODSB, затем помещает байт
значение на экране, чтобы создать пиксель этого цвета.

mov ах, 0
mov al, 13h
int 10h
; Вышеупомянутые 3 строки устанавливают видеорежим 13h

MOV BX, 0; Начать с пикселя 0
MOV CX, 200; Количество раз, чтобы пройти цикл
MOV SI, OFFSET MyBuffer; LODSB загружает байт из DS: SI в AL

LOOPER:
НАЖАТЬ CS
POP DS; Устанавливает DS в качестве текущего сегмента программы
CLD; Очистить флаг направления, чтобы SI увеличивался на LODSB
LODSB; Загрузить байт из DS: SI в AL и увеличить SI
MOV DS, 0A000h; сегмент видео с отображением в память VGA
MOV [BX], AL; Вывести данные на экран в виде пикселя
INC BX; перейти к следующему пикселю
DEC CX
CMP CX, 0
JNE LOOPER

mov ax, 4C00h; завершить программу
int 21h

MyBuffer DB 40 DUP (40, 49, 33, 44, 37)
; Создает 40 копий этой 5-байтовой последовательности (что соответствует цветам
; красный, зеленый, синий, желтый, пурпурный), на 200 байт в памяти
 

Показать файл необработанного изображения

Как расширение вышеуказанной программы, эта программа читает файл необработанного изображения (один
в котором каждый пиксель представляет собой отдельный байт файла) и выводит его на экран.Эта программа предполагает изображение размером 320×200 пикселей и, таким образом, использует данные размером 64000 байт.
буфер, но если вы используете другой размер изображения, вы можете настроить размер
этого буфера легко. Не забудьте указать имя файла в последней строке
(здесь установлено «IMAGE.RAW»).

mov ах, 0
mov al, 13h
int 10h
; Вышеупомянутые 3 строки устанавливают видеорежим 13h

MOV AH, 3Dh; открыть файл с помощью дескриптора
MOV AL, 0; только чтение
MOV DS, SEG Имя файла
MOV DX, OFFSET Имя файла
ИНТ 21ч
; AX теперь дескриптор файла

MOV BX, AX; дескриптор файла
MOV AH, 03Fh; Прочитать из файла с помощью дескриптора
MOV CX, 64000; количество байтов для чтения
MOV DS, SEG MyBuffer
MOV DX, СМЕЩЕНИЕ MyBuffer
ИНТ 21ч
; MyBuffer теперь должен содержать 64000 байтов файла изображения

MOV CX, 64000; Количество байтов для обработки
MOV BX, 0; Начать с пикселя 0
MOV SI, СМЕЩЕНИЕ MyBuffer

LOOPER:
НАЖАТЬ CS
POP DS; Устанавливает DS в качестве текущего сегмента программы
CLD; Очистить флаг направления, чтобы SI увеличивался на LODSB
LODSB; Загрузить байт из DS: SI в AL и увеличить SI
MOV DS, 0A000h; сегмент видео с отображением в память VGA
MOV [BX], AL; Вывести данные на экран в виде пикселя
INC BX; перейти к следующему пикселю
DEC CX
CMP CX, 0
JNE LOOPER

mov ax, 4C00h; завершить программу
int 21h

MyBuffer DB 64000 DUP (?)
Имя файла DB 'IMAGE.RAW ', 0
 

Сброс дат файлов

Было весело, но теперь давайте отойдем от мыши. Вот утилита для
сбросить даты файлов на 1 января 1980 г. в 12:00:00 (что является самым ранним
дата, поддерживаемая DOS в файлах). Очень удобно, когда хочется забыть о
когда что-то случилось.

; Следующие 5 строк просто сбрасывают флаг переноса (CF), что важно,
; поскольку очистка флага переноса заставляет INT 21,3D возвращать дескриптор файла
; вместо кода ошибки.
PUSHF; передать в стек 16-битный флаговый регистр
POP AX; AX теперь содержит содержимое регистра флагов
AND AX, 0FFFEh; устанавливает крайний правый бит (флаг переноса) в 0
НАЖАТЬ ТОПОР
POPF

MOV AH, 3Dh; открыть файл с помощью дескриптора
MOV AL, 1; только запись
MOV DS, SEG имя файла
MOV DX, OFFSET имя файла
ИНТ 21ч

; AX теперь должен содержать дескриптор файла.MOV BX, AX; передать дескриптор файла в BX, где он принадлежит
MOV AH, 57h; установить дату / время файла с помощью дескриптора
MOV AL, 1; установить (не читать) дату и время
MOV CX, 0; 12: 00: 00 AM
MOV DX, 21h; 1 января 1980 г. (0000000000100001)
ИНТ 21ч

MOV AH, 3Eh; закрыть файл с помощью дескриптора
; BX уже содержит соответствующий дескриптор файла из предыдущей операции,
; поэтому нам не нужно его переустанавливать.
ИНТ 21ч

mov ax, 4C00h; завершить программу
int 21h

; Отредактируйте строку ниже, чтобы она отражала фактическое имя файла
; файл, дату которого вы хотите сбросить.имя файла DB 'newfile.txt', 00
 

Корпус TSR

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

; КАК СДЕЛАТЬ TSR:
; ШАГ 1. Получить вектор прерывания INT 8.
CLI; отключить аппаратные прерывания
MOV AL, 8h; указывает INT 8
MOV AH, 35h; получить вектор прерывания
ИНТ 21ч
; ШАГ 2. Заставьте INT C8 вести себя как INT 8.; (INT C8 все равно ничем не используется.)
MOV DX, BX
НАЖАТЬ ES
POP DS
; указанные выше три строки устанавливают для DS: DX значение ES: BX.
MOV AL, 0C8h
MOV AH, 25h; установить вектор INT C8 (заставляет INT C8 вести себя как INT 8)
ИНТ 21ч
; ШАГ 3. Установите вектор INT 8 в вашу собственную функцию прерывания.
MOV DX, СМЕЩЕНИЕ TSRINT
НАЖМИТЕ CS; нажмите сегмент кода ...
POP DS; ... сделать DS равным сегменту этой подпрограммы
МОВ АЛ, 8
MOV AH, 25h; установите INT 8 как ваша новая функция прерывания
ИНТ 21ч
; ШАГ 4. Сделайте INT 21,31, и все готово.
MOV AL, 0; код выхода (для командных файлов)
MOV DX, 0FFh; сколько памяти зарезервировано для этого TSR
MOV AH, 31h; превратить его в TSR сейчас
ИНТ 21ч

ЦРИНТ:
; ЭТО ФАКТИЧЕСКИЙ КОД САМОСТОЯТЕЛЬНОГО ПРЕРЫВАНИЯ:
CLI; отключить прерывания
PUSHA; сохранить текущий статус регистров на будущее

; ВАША ПРОГРАММА ПРЕРЫВАНИЯ ЗДЕСЬ! Здесь код фактического прерывания
; функция (процесс, который TSR должен повторять, пока находится в
; память) должно уйти.; Напоминание: PUSHA используется выше для сохранения регистров, чтобы их можно было легко
; позже восстановили их прежнее состояние с помощью POPA. Однако PUSHA и POPA
; влияют только на (E) AX, (E) CX, (E) DX, (E) BX, (E) SP, (E) BP, (E) SI и (E) DI. Они
; НЕ влияют на какие-либо сегментные регистры (CS, DS, SS или ES), что означает, что
; если ваша процедура использует эти регистры, вам также необходимо сохранить их
; PUSH их перед этой подпрограммой прерывания и POPing после ее завершения.

POPA; восстановить старый статус реестра (чтобы все было как раньше)
INT 0C8h
; сделайте также старый INT 8, иначе то, что должно быть INT 8, никогда не будет выполнено

НАЖАТЬ ТОПОР
МОВ АЛ, 20ч
OUT 20h, AL; отправляет сигнал End-Of-Interrupt на контроллер 8259 int.
POP AX

STI; повторно разрешить прерывания (противоположно CLI)
IRET; возврат прерывания (означает, что прерывание закончилось)
 

Поддержание «постоянного пикселя» на экране

Хотя приведенная выше оболочка TSR является хорошей отправной точкой, на самом деле это не так.
Делать что-нибудь.Приведенная ниже программа, вероятно, одна из самых забавных маленьких
программы на ассемблере, которые я сделал, потому что это TSR, который что-то делает
это постоянно видно: он переключается в графический режим 13h, затем ставит
один желтый пиксель на экране. Что, пожалуй, еще интереснее
загружая эту программу, а затем экспериментируя с другими программами, которые используют
разные видеорежимы; в некоторых режимах видео из-за этого TSR несколько пикселей в
горизонтальная строка будет повреждена (так как некоторые графические режимы не используют
простое преобразование памяти из одного пикселя в один байт, которое используется в режиме 13h).

; TSR, который действительно что-то делает! Эта небольшая программа будет использовать свой
; Функциональность TSR для создания раздражающего пикселя на экране, который БУДЕТ
; НЕ уходите, сколько бы вы ни пытались его прокрутить или закрыть
;далеко. (Что ж, переключение в режим видео без графики прояснит это.)

mov ах, 00ч
mov al, 13h
int 10h

; Вышеупомянутые 3 строки переводят вас в режим видео 13

; ШАГ 1. Получить вектор прерывания INT 8.
CLI; отключить аппаратные прерывания
MOV AL, 8h; указывает INT 8
MOV AH, 35h; получить вектор прерывания
ИНТ 21ч
;ШАГ 2.Сделайте так, чтобы INT C8 вел себя как INT 8.
MOV DX, BX
НАЖАТЬ ES
POP DS
; указанные выше три строки устанавливают для DS: DX значение ES: BX.
MOV AL, 0C8h
MOV AH, 25h; установить вектор INT C8 (заставляет INT C8 вести себя как INT 8)
ИНТ 21ч
; ШАГ 3. Установите вектор INT 8 в вашу собственную функцию прерывания.
MOV DX, СМЕЩЕНИЕ TSRINT
НАЖМИТЕ CS; нажмите сегмент кода ...
POP DS; ... сделать DS равным сегменту этой подпрограммы
МОВ АЛ, 8
MOV AH, 25h; установите INT 8 как ваша новая функция прерывания
ИНТ 21ч
; ШАГ 4. Сделайте INT 21,31, и все готово.
MOV AL, 0; код выхода (для командных файлов)
MOV DX, 0FFh; сколько памяти зарезервировано для этого TSR
MOV AH, 31h; превратить его в TSR сейчас
ИНТ 21ч

ЦРИНТ:
CLI; отключить прерывания
PUSHA; сохранить текущий статус регистров на будущее
PUSH DS; сохраните DS тоже, так как мы его используем

; ПОРЯДОК ПОКАЗАНИЯ ПИКСЕЛЕЙ СЛЕДУЕТ!

MOV DS, 0A000h
MOV BX, 35100; расположение пикселя на экране.Настроить по вкусу.
MOV AX, 44h; 44h желтый
MOV [BX], AX

POP DS; поп DS ...
POPA; ... и все остальное, так что все как было раньше.
INT 0C8h; сделайте также старый INT 8

НАЖАТЬ ТОПОР
МОВ АЛ, 20ч
OUT 20h, AL; отправить сигнал End-Of-Interrupt на контроллер 8259 int
POP AX

STI; повторно разрешить прерывания
IRET; возврат прерывания (означает, что прерывание закончилось)
 

Укажите текущий видеорежим

Эта программа выводит один байт, который указывает текущее видео.
режим. Это может быть полезно, поскольку я не знаю другого способа
выяснить, в каком видеорежиме вы сейчас находитесь; вы также можете создать TSR
который делает что-то подобное по команде, чтобы выяснить, в каком видеорежиме
конкретная программа используется, поскольку она работает

; Показать текущий режим видео.Эта программа выводит один символ, который
; - это расширенный символ ASCII, соответствующий видеорежиму. В
; шестнадцатеричное значение символа - текущий видеорежим.

МОВ ДС, 40ч
МОВ СИ, 49ч

; DS: SI теперь указывает на 40:49, где находится текущее видео
;Режим

MOV DL, [SI]; DL = символ для вывода
МОВ АХ, 2
INT 21h; вывести символ

mov ax, 4C00h; завершить программу
int 21h
 

Распечатать дату BIOS

Стандартный компьютер BIOS хранит дату выпуска в виде 8-байтовой строки в памяти.
расположение FFFF: 5.Эта программа печатает эту строку.

; Печать из ПЗУ, начиная с FFFF: 5 для 8 символов
; (Здесь отображается дата ROM BIOS)
MOV DS, 0FFFFh
МОВ СИ, 5
MOV CX, 8

mainloop:
MOV DL, [SI]; DL = символ для вывода
МОВ АХ, 2
INT 21h; вывести символ
INC SI; переместить SI на следующий байт
LOOPNZ mainloop; это повторяется 8 раз, потому что CX был установлен на 8

mov ax, 4C00h; завершить программу
int 21h
 

Нарисовать пиксель в графическом режиме

По умолчанию запросы DOS являются только текстовыми, что означает, что вы не можете рисовать графику на
экран с ними.Однако вы можете изменить свой графический режим с помощью INT.
10,0. Эта программа начинается с переключения в знаменитый режим 13 (320×200
256-цветной VGA, очень популярный видеорежим для любительских игр DOS), а затем
рисует один пиксель, чтобы продемонстрировать графические возможности этого
режим.

; Рисует один желтый пиксель в верхнем левом углу.
; Записывает непосредственно в видеопамять, которая начинается с A000: 0

mov ах, 00ч
mov al, 13h
int 10h

; Вышеупомянутые три строки просто переключаются на 256-цветной VGA 320x200.

mov ds, 40960
; a000h = 40960 десятичное
мов топор, 44ч
; 44h - желтый! ;)
mov bx, 0000
mov [bx], топор

; По умолчанию это DS: [что угодно], и поскольку все, что здесь есть bx, оно перемещает
; AX в DS: BX !!!

mov ax, 4C00h; завершить программу
int 21h
 

Нарисовать линию в графическом режиме

; В дальнейшем рисует желтую линию в верхнем левом углу.; Хороший пример того, как эффективно использовать INC, CMP,
; и условный переход для повторяющихся задач.

mov ах, 00ч
mov al, 13h
int 10h

; Вышеупомянутые три строки просто переключаются на 256-цветной VGA 320x200.

mov ds, 40960
; a000h = 40960 десятичное
мов топор, 44ч
; 44h - желтый! ;)
mov bx, 0000
НАЧАЛО:
mov [bx], топор
inc bx
cmp bx, 20
JL START

; Ожидается, пока BX не достигнет 20, а затем завершается!

mov ax, 4C00h; завершить программу
int 21h
 

Нарисовать по одному пикселю каждого цвета, доступного в режиме видео 13

; печатает подряд все 256 цветов в порядке от 1 до FF,
; в верхней части экрана.mov ах, 00ч
mov al, 13h
int 10h

; Вышеупомянутые три строки просто переключаются на 256-цветной VGA 320x200.

mov ds, 40960
; a000h = 40960 десятичное
mov bx, 0000

mov ax, 00h; Начните с цвета номер 0, поскольку 0 также является допустимым цветом.
MAINLOOP:
mov [bx], топор
inc bx
вкл топор
cmp ax, 0FFh
JNG MAINLOOP

mov ax, 4C00h; завершить программу
int 21h
 

Нарисуйте пять рядов пикселей каждого цвета, доступного в режиме видео 13

; Печать всех возможных 256 цветов подряд 5 раз (таким образом получается 5 строк).
;
; Один пиксель трудно различить, поэтому я подумал, что это будет полезно
; чтобы цветные полосы были больше 1 пикселя.; Однако VGA с разрешением 320x200 имеет только 320 пикселей по горизонтали, поэтому нет
; возможность увеличить на 256 цветов по горизонтали и при этом сохранить их все
; в один ряд. Таким образом, я выбрал вертикальный вариант.

mov ах, 00ч
mov al, 13h
int 10h

; Вышеупомянутые три строки просто переключаются на 256-цветной VGA 320x200.

mov ds, 40960
; a000h = 40960 десятичное
mov bx, 0000

mov ax, 00h; Начните с цвета номер 0, поскольку 0 также является допустимым цветом.
MAINLOOP:
mov [bx], топор
добавить bx, 320
; Выше мы используем десятичное значение горизонтального разрешения для спуска на строку
mov [bx], топор
добавить bx, 320
mov [bx], топор
добавить bx, 320
mov [bx], топор
добавить bx, 320
mov [bx], топор
sub bx, 1280; А теперь вычтите 4 раза горизонтальное разрешение, чтобы подняться на 4 строки
inc bx
вкл топор
cmp ax, 0FFh
JNG MAINLOOP

mov ax, 4C00h; завершить программу
int 21h
 

Включить «блокировку» всей клавиатуры

Эта программа включает Caps Lock, Num Lock и Scroll Lock.

; Переместите F0h на 40:17

МОВ ДС, 40ч
MOV BX, 17ч
MOV AX, 0F0h
MOV [BX], AX

mov ax, 4C00h; завершить программу
int 21h
 

Отключить «блокировку» всей клавиатуры

; Перенести 80h на 40:17

МОВ ДС, 40ч
MOV BX, 17ч
MOV AX, 80ч
MOV [BX], AX

mov ax, 4C00h; завершить программу
int 21h
 

Ожидание определенного скан-кода клавиатуры

; Продолжайте сканировать порт ввода клавиатуры (порт 60h). Это следит за
; какие бы клавиши ни были нажаты. Если ключ является ключом A (который имеет сканирование
; код 1Eh), программа завершается.Это демонстрирует, как напрямую просматривать
; какие клавиши нажимаются.
петлитель:
IN AL, 60h; получить самый последний скан-код

CMP AL, 1Eh; это?
JE эндер
Петлитель JMP

конец:
MOV AX, 4C00h; завершить программу
ИНТ 21ч
 

Показать содержимое буфера клавиатуры кольцевой очереди

Очевидно, ячейка памяти 40: 1E должна содержать «кольцевую очередь
буфер клавиатуры «. Я так и не понял, как это работает и что
форматировать содержимое этого места должно быть, но это
интересно запустить эту программу и посмотреть несколько исторических нажатий клавиш
вверх.

; 40: 1E - расположение 32-байтового буфера клавиатуры кольцевой очереди
MOV DS, 0040h
MOV SI, 001Eh
MOV BH, 0001h
MOV AH, 0Eh
MOV CX, 32ч

MainLoop:
MOV AL, [SI]
ИНТ 10ч
INC SI
LOOP MainLoop

mov ax, 4C00h; завершить программу
int 21h
 

Показать параметры командной строки

В какой-то момент вы можете захотеть создать программу, которая будет использовать
параметры командной строки. Эта программа позволяет вам экспериментировать с параметрами, так как
он просто распечатывает параметры, которые вы даете ему во время выполнения.

; Показать параметры командной строки, заданные программе

MOV AH, 03h
INT 10h; Это вернет: строка курсора: DH, столбец курсора: DL

МОВ СИ, 80ч
CMP B [SI], 00ч.
Дж. Э. Эндер

; Три вышеприведенных строки быстро проверяют, заданы ли * какие-либо * параметры.
; Если нет, выполняется резкий выход.
; (Количество байтов в параметрах хранится в 80h)

MOV SI, 82h; Начиная с 82h, параметры командной строки

MOV CX, 1h; Для INT 10, ради A

MainLoop:
MOV AH, 0Ah; Указывает INT 10 напечатать символ
MOV AL, [SI]; Загружает символ в AL
CMP AL, 0Dh; Если символ - возврат каретки, конец
Дж. Э. Эндер; (Потому что это конец параметров)
INT 10h; печатает символ в AL
INC SI; перейти к следующему символу
INC DL
MOV AH, 02h
ИНТ 10ч
; Три вышеперечисленных строки перемещают столбец на один вправо
JMP MainLoop; продолжайте, пока не дойдет до возврата каретки

Эндер:
mov al, 0
; Забавно, но это вызывает проблемы для INT 21,4C, если у AL есть
; отвратительное значение, поэтому мы сбрасываем его непосредственно перед завершением.mov ax, 4C00h; завершить программу
int 21h
 

Включите внутренний динамик ПК

; Включает внутренний динамик.

MOV AL, 0B6h
ВЫХОД 43ч, AL

; Большинство источников говорят, что вы должны отправить шестнадцатеричный B6 на порт 43 перед
; включение динамика. Хотя кажется, что он работает нормально, даже если вы этого не сделаете,
; вы могли бы также.

IN AL, 61h; Устанавливает AL на текущее значение порта 61
ИЛИ AL, 11b; Устанавливает последние два байта AL в 1
ВЫХОД 61ч, AL

; Получается 61 именно так, как было, но с двухбайтовой заменой!
; Это включит динамик!

MOV AX, 4C00h; завершить программу
ИНТ 21ч
 

Выключите внутренний динамик ПК

; Выключает внутренний динамик.IN AL, 61h; Устанавливает AL на текущее значение порта 61
XOR AL, 11b; Устанавливает последние два байта AL в 0
ВЫХОД 61ч, AL

; Получается 61 именно так, как было, но с двухбайтовой заменой!
; Это отключит динамик!

MOV AX, 4C00h; завершить программу
ИНТ 21ч
 

Изменение высоты звука внутреннего динамика ПК

; Изменяет тембр динамика компьютера. (Должно быть
; уже был включен раньше.)

МОВ АХ, 11
MOV AL, 01011101xB

ВЫХОД 42ч, AL
MOV AL, AH
ВЫХОД 42ч, AL

mov ax, 4C00h; завершить программу
int 21h
 

Превратить первого пилота в ростере Gunship 2000 во 2-го лейтенанта

Отличное дополнение

Gunship 2000, Islands And Ice, позволяет использовать только его
редактор миссии после того, как вы достигли звания не ниже 2-го лейтенанта
(2LT).Это, честно говоря, возмутительно, поскольку вы должны уметь
собственные миссии в любое время. Эта программа — простой способ справиться с этим
проблема. Он редактирует файл реестра Gunship 2000, ROSTER.DAT, так что первый
Пилот в составе 2-го лейтенанта. Если вы хотите сменить пилота
кроме первого в списке, вы можете просто отредактировать количество байтов
который программа продвигает в файле. В настоящее время он установлен на 54ч.

; Установите для байта 54h файла значение 5. Это сделает вас 2-м лейтенантом (2LT),
; что, в свою очередь, позволит вам использовать конструктор миссий.MOV AH, 3Dh; открыть файл с помощью дескриптора
MOV AL, 01; только запись
MOV DS, SEG FILENAME
MOV DX, СМЕЩЕНИЕ ИМЕНИ ФАЙЛА
ИНТ 21ч

; AX теперь дескриптор файла

MOV BX, AX; перемещает AX (дескриптор файла) в BX
MOV AH, 42h; переместить указатель файла
МОВ АЛ, 0
MOV CX, 0; старшее слово количества байтов для перемещения
MOV DX, 54h; младшее слово количества байтов для перемещения
; Переместите 54 байта, так как именно там находится нужный нам байт
ИНТ 21ч

; BX по-прежнему остается дескриптором файла
MOV AH, 40h; записать в файл с помощью дескриптора
MOV CX, 1; количество байтов для записи
MOV DS, SEG БУФЕР
MOV DX, СМЕЩЕНИЕ БУФЕРА
ИНТ 21ч

mov ax, 4C00h; завершить программу
int 21h

ИМЯ ФАЙЛА db 'ROSTER.ДАТ ', 00
; 00 в конце означает завершение имени файла символом 00,
; превращая его в строку ASCIIZ.

БУФЕР db 5
; BUFFER - это то, что на самом деле будет записано в файл.
 

Ну вот и все. Если у вас есть какие-либо советы или комментарии, пожалуйста
Напиши мне. Спасибо.

Вернуться на главную

Синтаксис сборки AT&T | Sig9

Обновлено: 10 мая 2006 г.

Эта статья представляет собой краткое введение в сборку AT&T.
синтаксис языка, реализованный в GNU Assembler как (1) .Для первого таймера синтаксис AT&T может показаться немного запутанным, но
если у вас есть опыт программирования на ассемблере, это
легко догнать, если у вас есть несколько правил. Я полагаю, у вас есть
некоторое знакомство с тем, что обычно называют синтаксисом INTEL
для инструкций на языке ассемблера, как описано в руководствах x86.
Из-за его простоты я использую вариант NASM (Netwide Assembler)
INTEL-синтаксис для указания различий между форматами.

Ассемблер GNU является частью двоичных утилит GNU (binutils) и серверной частью коллекции компиляторов GNU. Хотя как
не является предпочтительным ассемблером для написания достаточно большого ассемблера
программы, это жизненно важная часть современных Unix-подобных систем,
особенно для взлома на уровне ядра. Часто критикуют за загадочность
Синтаксис в стиле AT&T, утверждается, что как был написан с
акцент на использовании в качестве серверной части для GCC, мало заботясь о
«удобство для разработчиков».Если вы программист на ассемблере, то приветствуете
на фоне синтаксиса INTEL вы испытаете некоторую удушающую
в отношении читаемости кода и генерации кода. Тем не менее, это
Следует отметить, что кодовая база многих операционных систем зависит от как в качестве ассемблера для генерации низкоуровневого кода.

Базовый формат

Структура программы в AT&T-синтаксисе аналогична любой
другой ассемблер-синтаксис, состоящий из серии директив, меток,
инструкции — состоящие из мнемоники, за которой следуют не более трех
операнды.Наиболее заметное различие в основах синтаксиса AT и T
от порядка операндов.

Например, общий формат основной инструкции перемещения данных в INTEL-синтаксисе:

 мнемоническое назначение, источник 

, тогда как в случае AT&T общий формат —

 мнемонический источник, пункт назначения 

Некоторым (включая меня) этот формат более интуитивно понятен. В
в следующих разделах описаны типы операндов ассемблера AT&T.
инструкция для архитектуры x86.

Регистры

Все имена регистров архитектуры IA-32 должны иметь префикс ‘%’, например. % al,% bx,% ds,% cr0 и т. д.

 mov% ax,% bx 

Приведенный выше пример — это инструкция mov, которая перемещает значение из 16-битного регистра AX в 16-битный регистр BX.

Буквенные значения

Все буквальные значения должны начинаться со знака «$». Например,

mov $ 100,% bx
mov $ A,% al 

Первая инструкция перемещает значение 100 в регистр AX.
а второй перемещает числовое значение ascii A в AL
регистр.Чтобы было понятнее, обратите внимание, что приведенный ниже пример не является
действующая инструкция,

 mov% bx, $ 100 

, поскольку он просто пытается переместить значение в регистре bx в буквальное значение. В этом нет никакого смысла.

Адресация памяти

В синтаксисе AT&T память упоминается следующим образом:

 переопределение сегмента: смещение со знаком (основание, индекс, масштаб) 

части, из которых можно не указывать в зависимости от желаемого адреса.

% es: 100 (% eax,% ebx, 2) 

Обратите внимание, что смещения и масштаб не должны начинаться с префикса
‘$’.Еще несколько примеров с их эквивалентным синтаксисом NASM, должны сделать
вещи яснее,

 Операнд памяти GAS Операнд памяти NASM
------------------ -------------------

100 [100]
% es: 100 [es: 100]
(% eax) [eax]
(% eax,% ebx) [eax + ebx]
(% ecx,% ebx, 2) [ecx + ebx * 2]
(,% ebx, 2) [ebx * 2]
-10 (% eax) [eax-10]
% ds: -10 (% ebp) [ds: ebp-10] 

Пример инструкции,

 mov% ax, 100
mov% eax, -100 (% eax)
 

Первая инструкция перемещает значение в регистре AX на смещение 100
регистра сегмента данных (по умолчанию), а второй перемещает
значение в регистре eax до [eax-100].

Размеры операндов

Иногда, особенно при перемещении буквальных значений в память,
становится необходимым указать размер передачи или размер операнда.
Например инструкция,

 mov $ 10, 100 

только указывает, что значение 10 должно быть перемещено в смещение памяти.
100, но не размер перевода. В NASM это делается путем добавления
приведение ключевого слова byte / word / dword и т. д. к любому из операндов. В
Синтаксис AT&T, это делается путем добавления суффикса — b / w / l — к
инструкция.Например,

 movb $ 10,% es: (% eax) 

перемещает байтовое значение 10 в ячейку памяти [ea: eax], тогда как

 movl $ 10,% es: (% eax) 

перемещает длинное значение (двойное слово) 10 на то же место.

Еще несколько примеров,

 movl $ 100,% ebx
pushl% eax
popw% топор 

Инструкции по передаче управления

Инструкции jmp, call, ret и т. Д. Передают управление из одного
часть программы в другую. Их можно отнести к контрольным.
переводит в один и тот же сегмент кода (рядом) или в разные сегменты кода
(далеко).Возможные типы адресации ветвей: относительное смещение.
(метка), регистр, операнд памяти и указатели смещения сегмента.

Относительные смещения указываются с помощью меток, как показано ниже.

 label1:
.
.
  jmp label1
 

Адресация ветвей с использованием регистров или операндов памяти должна быть
с префиксом ‘*’. Чтобы указать «дальние» передачи управления, необходимо указать «l».
с префиксом, как в ‘ljmp’, ‘lcall’ и т. д. Например,

 Синтаксис GAS Синтаксис NASM
========== ===========

jmp * 100 jmp рядом с [100]
звоните * 100 звоните рядом с [100]
jmp *% eax jmp рядом с eax
jmp *% ecx вызов около ecx
jmp * (% eax) jmp около [eax]
звонок * (% ebx) звонок рядом с [ebx]
ljmp * 100 jmp далеко [100]
lcall * 100 дальний вызов [100]
ljmp * (% eax) jmp far [eax]
lcall * (% ebx) дальний вызов [ebx]
ret retn
lret retf
lret $ 0x100 retf 0x100

 

Смещение сегмента указателей указывается в следующем формате:

 jmp $ сегмент, $ смещение 

Например:

 jmp $ 0x10, $ 0x100000 

Если вы помните об этих нескольких вещах, вы наверняка наверстаете упущенное.Что касается более подробной информации об ассемблере GNU, вы можете попробовать документацию.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *