-
Notifications
You must be signed in to change notification settings - Fork 2
Лекция 3
Inspirate789 edited this page May 15, 2022
·
8 revisions
CMOVcc <приёмник>, <источник>
- пересылает данные только при выполнении условий, аналогичных командам условного перехода (переход произойдёт только если стоят нужные флаги).
нельзя обменивать значения двух переменных, так как на шину адреса нельзя выставить 2 адреса (она одна и работает только в одну сторону - либо читаем, либо пишем)
XLAT [адрес]
(принято писать адрес, но использоваться будет только сегментная часть, которая может собой заменять DS
)
- Помещает в
AL
байт из таблицы (массив, который не превышает 256 ячеек памяти) по адресуDS:BX
со смещением относительно начала таблицы, равнымAL
. - Адрес, указанный в исходном коде, не обрабатывается компилятором и служит в качестве комментария.
- Если в адресе явно указан сегментный регистр, он будет использоваться вместо
DS
.
- Транслитерация (перевод английских символов в русские один к одному)
- Перевод шестнадцатеричных чисел в символы
- Вычисляет эффективный адрес источника и помещает его в приёмник (в данном случае адрес === смещение).
- Позволяет вычислить адрес, описанный сложным методом адресации. Поддерживает на аппаратном уровне (на уровне процессора) сложные формулы с несколькими операндами: сложение с константой и сложение/умножение каких-то регистров. Работает быстрее арифметических команд и не меняет флаги.
- Иногда используется для быстрых арифметических вычислений:
lea bx, [bx+bx*4] lea bx, [ax+12]
- Эти вычисления занимают меньше памяти, чем соответствующие MOV и ADD, и не изменяют флаги.
- Вычисление адреса "на лету"
- Быстрая арифметика (не нужно писать несколько арифметических команд)
-
ADD
,SUB
не делают различий между знаковыми и беззнаковыми числами. -
ADC <приёмник>, <источник>
- сложение с переносом. Складывает приёмник, источник и флаг CF. -
SBB <приёмник>, <источник>
- вычитание с займом. Вычитает из приёмника источник и дополнительно - флаг CF.
Пример: сложение и вычитание 32-разрядных чисел: два числа, разбитые на старшие и младшие половинки, лежат в парах регистров DX и AX, BX и CX.
add ax, cx ; Складываем младшие половинки (могло произойти переполнение с установкой флага CF)
adc dx, bx ; Складываем старшие половинки с учётом флага CF, не прописывая руками никаких условных операторов.
sub ax, cx ; Вычитаем младшие половинки (разность может быть отрицательной, тогда установится флаг CF)
sbb dx, bx ; Вычитаем старшие половинки с учётом флага CF, не прописывая руками никаких условных операторов.
IMUL <источник>
-
IMUL <приёмник>, <источник>
- не из нотации процессора 8086, а из последующих -
IMUL <приёмник>, <источник1>, <источник2>
- не из нотации процессора 8086, а из последующих
IDIV <источник>
INC <приёмник>
DEC <приёмник>
- Увеличивает/уменьшает приёмник на 1.
- В отличие от
ADD
, не изменяетCF
. -
OF
,SF
,ZF
,AF
,PF
устанавливаются в соответствии с результатом.
NEG <приёмник>
- Переводит число в дополнительный код и прибавляет к нему 1, чтобы получить число с противоположным знаком.
Двоично-десятичные числа используются в аппаратуре, которая должна на дисплей выводить десятичные числа, не занимаясь дополнительными вычислениями (переводом). Пример: часы с лампочками на 1 этаже в бауманке.
- Неупакованное двоично-десятичное число - байт от 00h до 09h.
- Упакованное двоично-десятичное число - байт от 00h до 99h (цифры A..F не задействуются).
- При выполнении арифметических операций необходима коррекция:
19h + 1 = 1Ah => 20h
inc al
daa
Арифметический сдвиг (число сдвигается с учётом знака, т.е. знак сохраняется: -8>>2 = -4
): SAR
(Shift Arithmetic Right), SAL
(Shift Arithmetic Left).
Логический сдвиг (знак не сохраняется, т.е. знаковый бит сдвигается так же, как и все остальные): SHR
(SHift Right), SHL
(SHift Left).
-
SAL
тождественнаSHL
. -
SHR
зануляет старший бит (знак),SAR
- сохраняет. -
ROR
,ROL
- циклический сдвиг вправо/влево. -
RCR
,RCL
- циклический сдвиг черезCF
(в операции участвует не 16 бит, а 17 - при сдвиге вправо младший бит попадёт вCF
, аCF
- в старший бит, при сдвиге влево всё произойдёт наоборот).
OFFTOP:
Пример битовой строки - маска подсети в IP-адресе.
Также маски используются для обнуления, установления значений отдельных битов и т.д. ...
Регистр флагов также можно рассматривать как битовую строку.
-
BTS <база>, <смещение>
- считывает во флагCF
значение из битовой строки. -
BTS <база>, <смещение>
- установить бит в 1. -
BTR <база>, <смещение>
- сбросить бит в 0. -
BTC <база>, <смещение>
- инвертировать бит. -
BSF <приёмник>, <источник>
- прямой поиск бита (от младшего разряда к старшему, т.е. справа налево). -
BSR <приёмник>, <источник>
- обратный поиск бита (от старшего разряда к младшему, т.е. слева направо). -
SETcc <приёмник>
- выставляет приёмник (1 байт) в 1 или 0 в зависимости от условия, аналогичноJcc
.
-
LOOP <метка>
- уменьшаетCX
и выполняет "короткий" переход на метку, еслиCX
не равен нулю. -
LOOPE/LOOPZ <метка>
- цикл "пока равно"/"пока ноль" -
LOOPNE/LOOPNZ <метка>
- цикл "пока не равно"/"пока не ноль"
- Сишный: строка неограниченной длины, завершающаяся нулевым байтом (т.е. не может содержать в себе нулевой байт)
- Паскалевский: в первых 1 или 2 байтах хранится длина строки, а затем идут сами символы. Тут мы заранее знаем длину строк (не нужно вычислять длину за O(n), как в Си это делает
strlen
), но эта длина ограничена.
-
MOVS/MOVSB/MOVSW <приёмник>, <источник>
- копирование (На самом деле эта команда не имеет параметров. Она всегда пишет данные изDS:SI
вES:DI
) -
CMPS/CMPSB/CMPSW <приёмник>, <источник>
- сравнение и выставление флага по его результатам -
SCAS/SCASB/SCASW <источник>
- сканирование источника (всегда этоDS:SI
), сравнение сAL/AX
и выставление флага по его результатам -
LODS/LODSB/LODSW <источник>
- чтение (вAL/AX
) -
STOS/STOSB/STOSW <приёмник>
- запись (изAL/AX
)
Каждая такая команда делает сразу несколько действий (работают за несколько тактов процессора; на аппаратном уровне разбиты на атомарные команды):
- Читает из памяти по адресу источника (например
DS:SI
;SI
- Source Index) 1, 2 или 4 байта (в зависимости от команды) - Увеличивает индексы
SI
иDI
на 1, 2 или 4 - Если команда связана с записью, то произойдёт запись 1, 2 или 4 байт по адресу приёмника (
ES:DI
;DI
- Destination Index)
Среди перечисленных команд есть те, которые работают и с источником, и с приёмником, и те, которые работают только с источником или только с приёмником.
Эти команды работают только с отдельными байтами, машинными словами или двойными словами. Чтобы работать со всей строкой, нужно использовать префиксы
.
Применение: пишем префикс перед командой (через пробел), тогда команда будет выполняться в цикле со счётчиком CX
, как команда loop
.
Префиксы REPE
/REPZ
/REPNE
/REPNZ
дополнительно будут контролировать состояние флага ZF
(т.е., например, REPZ
будет выполнять цикл до тех пор, пока CX
не равен 0 и ZF
равен 0; остальное по аналогии).
С помощью команды SCASB
и префиксов можно производить поиск конца строки (положить в CX
заведомо очень большое положительное число, которое точно больше длины нашей строки и запустить долгий цикл поиска конца строки; в результате в CX
будет лежать разница между исходным значением CX
и длиной нашей строки).
-
STC
/CLC
/CMC
- установить/сбросить/инвертироватьCF
-
STD
/CLD
- установить/сброситьDF
(Direction Flag); Если он равен нулю (по умолчанию), то обработка строк будет идти слева направо, т.е. по возрастанию адресов памяти, иначе наоборот -
LAHF
- загрузка флагов состояния вAH
-
SAHF
- установка флагов состояния изAH
-
CLI
/STI
- запрет/разрешение прерываний (т.е. сброса/установкиIF
- Interruption Flag). Разумеется речь идёт не про программные, а про аппаратные прерывания.
(ведь на обычные программы они вообще-то не влияют)
При выполнении критических процессов в какой-нибудь системной программе (например, настройка ОС и самой таблицы прерываний - тогда адреса обработчиков прерываний ещё не установлены и, соответственно, процессор обратиться к ним не может) срабатывание прерывания, которое сейчас не настроено, может оказаться критическим (например, привести к краху системы или к выполнению какого-то случайно вызванного кода).
Разумеется, делать это нужно на как можно более маленький промежуток времени, потому что если прерывание произойдёт тогда, когда оно запрещено, то с какой-то вероятностью данные, связанные с этим прерыванием, потеряются
-
LDS <приёмник>, <источник>
- загрузить адрес, используяDS
-
LES <приёмник>, <источник>
- загрузить адрес, используяES
-
LFS <приёмник>, <источник>
- загрузить адрес, используяFS
-
LGS <приёмник>, <источник>
- загрузить адрес, используяGS
-
LSS <приёмник>, <источник>
- загрузить адрес, используяSS
SP
(Stack Pointer - указатель на вершину стека), BP
(Base Pointer - вспомогательный регистр, используемый программистами и компиляторами для составления подпрограмм)
- LIFO/FILO (last in, first out) - последним пришёл, первым ушёл
- Сегмент стека - область памяти программы, используемая её подпрограммами, а также (вынужденно) обработчиками прерываний
-
SP
- указатель на вершину стека - В x86 стек "растёт вниз", в сторону уменьшения адресов (от старших адресов к младшим) (от конца сегмента к началу). В таком случае удобно определять переполнение стека, т.е. нужно просто отследить момент, когда
SP
стал равен нулю. Если бы этой механики не было, то приходилось бы где-то хранить размер стека. - При запуске программы
SP
указывает на конец сегмента.
Каждая такая команда делает сразу несколько действий (работают за несколько тактов процессора; на аппаратном уровне разбиты на атомарные команды):
- Уменьшает указатель вершины стека (регистр
SP
) на размер источника (того, что мы кладём в стек) - Записывает значение из источника по адресу
SS:SP
- Считывает значение из вершины стека (с адреса
SS:SP
) и записывает его в приёмник - Увеличивает указатель вершины стека (регистр
SP
) на размер приёмника (того, что мы достаём из стека)
-
PUSH <источник>
- поместить данные в стек. УменьшаетSP
на размер источника и записывает значение по адресуSS:SP
. -
POP <приёмник>
- считать данные из стека. Считывает значение с адресаSS:SP
и увеличиваетSP
. -
PUSHA
- поместить в стек регистрыAX
,CX
,DX
,BX
,SP
,BP
,SI
,DI
. -
POPA
- загрузить регистры из стека (SP
игнорируется) -
PUSHF
- поместить в стек содержимое регистра флагов -
POPF
- загрузить регистр флагов из стека
- Она похожа на
PUSH
(но немного сложнее): сохраняет адрес следующей за ней команды в стеке (той, которая физически за ней стоит в памяти, а не та, которая вызывается при переходе в подпрограмму), уменьшаетSP
(в случае ближнего перехода на 2 байта, в случае дальнего - на 4 байта) и записывает по этому адресу значениеIP
либоCS:IP
, в зависимости от размера аргумента. Далее управление программы передаётся на значение операнда (в регистрIP
записывается смещение подпрограммы, а в случае дальнего перехода оно заносится ещё и вCS:IP
; насчёт дальнего перехода не уверен, там не очень чёткая формулировка была)
Адрес возврата записывается именно в стек, а не в регистры, так как если бы мы записывали его в регистры, то мы бы не смогли сделать длинную рекурсию (регистров бы не хватило).
- Передаёт управление на значение аргумента.
- Загружает из стека адрес возврата в регистр
IP
или, в случае дальнего перехода, вCS:IP
, у - Увеличивает
SP
(на 2/4 байта в случае ближнего/дальнего перехода)
Если указан операнд, его значение будет дополнительно прибавлено к SP
для очистки стека от параметров.
- Сохранение "начального" адреса
SP
, т.е. в самом начале подпрограммы программист сам должен написатьMOV BP, SP
, чтобы потом тоже можно было что-то писать в стек, а исходное значениеSP
сохранилось бы для нас. - Адресация параметров (то, что положила в стек вызывающая подпрограмма)
- Адресация локальных переменных
Глобальные переменные хранятся в сегменте данных. Локальные переменные тоже можно было бы хранить там, но тогда при каждом рекурсивном вызове они бы затирались, поэтому локальные переменные хранятся в стеке.
То есть, перед вызовом подпрограммы вызывающая программа кладёт в стек параметры, затем выполняет команду CALL
, подпрограмма сама в своём начале сохраняет текущее значение регистра SP
в регистре BP
и дополнительно уменьшает SP
на размер области памяти, необходимый для хранения локальных переменных.
Здесь совершается ближний переход, т.е. перемещение в пределах одного сегмента (SP
уменьшается на 2)
Один из самых распространённых способов возврата значения из подпрограммы, если это значение числовое, - через регистр AX
.
Вместо POP DX
(перемещения параметра из стека в неиспользуемый регистр) можно было бы просто увеличить SP
на 2. Результат один и тот же - очищение стека от локального параметра.
Фрейм (Frame) вызова подпрограммы (flashback с кадром стека в лекциях по Си) - область стека от параметров до локальных переменных (немного вольное определение по-моему).
Если подпрограмма вызывается рекурсивно, то каждый её вызов будет порождать очередной фрейм и выделять место в стеке.
- Стек очищает вызывающая сторона
- Числа возвращаются через регистр
AX
- Большие числа или строки будут возвращаться через указатель
Стековый кадр (фрейм) — механизм передачи аргументов и выделения временной памяти с использованием аппаратного стека. Содержит информацию о состоянии подпрограммы.
- Параметры
- Адрес возврата (обязательно)
- Локальные переменные