-
Notifications
You must be signed in to change notification settings - Fork 2
Вопросы к РК1
Здесь ответы на вопросы предыдущих годов. За них я не отвечаю.
Предназначены для временного хранения данных, записи параметров машинных команд, арифметической обработки и т.д.
Существует всего 4 РОН: AX, BX, CX, DX. Каждый содержит в себе 16 бит и делится на 2 части по 8 бит - старшую (high, H) и младшую (low, L).
Обращаться можно как к регистру целиком, так и к его половинам по отдельности.
- AX (AH + AL): аккумулятор - умножение, деление, обмен с устройствами ввода/вывода (команды ввода и вывода);
- BX (BH + BL): базовый регистр в вычислениях адреса, часто указывает на начальный адрес (называемый базой) структуры в памяти;
- CX (CH + CL): счетчик циклов, определяет количество повторов некоторой операции;
- DX (DH + DL): определение адреса ввода/вывода, так же может содержать данные, передаваемые для обработки в подпрограммы.
Регистры процессора — блок ячеек памяти, образующий сверхбыструю оперативную память внутри процессора. Большинство команд процессора манипулируют данными, хранящимися в регистрах.
Физический адрес получается сложением адреса начала сегмента (на основе сегментного регистра) и смещения.
Сегментный регистр хранит в себе старшие 16 разрядов (из 20) адреса начала сегмента. 4 младших разряда в адресе начала сегмента всегда нулевые. Говорят, что сегментный регистр содержит в себе номер параграфа начала сегмента.
| Номер параграфа начала сегмента |
19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
| Смещение |
[SEG]:[OFFSET]
=> физический адрес:
- SEG необходимо побитово сдвинуть на 4 разряда влево (или умножить на 16, что тождественно)
- К результату прибавить OFFSET
5678h:1234h =>
56780
+ 1234
= 579B4
Распространённые пары регистров: CS:IP
(указатель на следующую команду), DS:BX
(указатель на некоторую переменную), SS:SP
(указатель на вершину стека).
- Модули (файлы исходного кода)
- Сегменты (описание блоков памяти)
- команды процессора;
- инструкции описания структур данных, выделения памяти для переменных и констант;
- макроопределения.
- Сегменты (описание блоков памяти)
- сегмент кода (CS)
- сегмент данных (DS и дополнительные ES, FS, GS)
- сегмент стека (SS)
имя SEGMENT [READONLY] [выравнивание] [тип] [разрядность] [‘класс’]
...
имя ENDS
-
BYTE
(сегмент может начинаться с произвольного адреса) -
WORD
(Адрес начала сегмента кратен 2 байтам) -
DWORD
(Адрес начала сегмента кратен 4 байтам) -
PARA
(сегмент располагается в начале параграфа, т.е. адрес начала сегмента кратен 16 байтам) - по умолчанию -
PAGE
(сегмент располагается в начале страницы, т.е. адрес начала сегмента кратен 256 байтам)
-
PUBLIC
(сегменты с одним именем будут располагаться в памяти друг за другом независимо от порядка объявления) -
STACK
(сегмент будет использоваться под стек (сегмент стека), все такие сегменты будут объединяться в один, увеличивая общий стек) -
COMMON
(сегменты будут накладываться, т.е. начинаться с одного и того же адреса; память выделится по размеру наибольшего из сегментов; метки, на которые ссылаются сегменты, будут указывать на одни и те же ячейки памяти) -
AT
(нужен аргумент - номер параграфа начала сегмента; сегмент будет загружаться в память по некоторому фиксированному постоянному адресу, независимо от расположения остальных сегментов) -
PRIVATE
(сегмент не объединяется с другими) - по умолчанию
Класс - любая метка, взятая в одинарные кавычки. Сегменты одного класса будут расположены в памяти друг за другом.
Прерывание - особая ситуация, когда выполнение текущей программы приостанавливается и управление передаётся программе-обработчику возникшего прерывания.
- аппаратные (асинхронные) - события от внешних устройств;
- внутренние (синхронные) - события в самом процессоре, например, деление на ноль;
- программные - вызванные командой
INT
;
- Аналог системного вызова в современных ОС.
- Используется наподобие вызова подпрограммы.
- Номер функции передаётся через
AH
.
Функция | Назначение | Вход | Выход |
---|---|---|---|
02 | Вывод символа в stdout | DL = ASCII-код символа | - |
09 | Вывод строки в stdout | DS:DX - адрес строки, заканчивающейся символом $ | - |
Функция | Назначение | Вход | Выход |
---|---|---|---|
01 | Считать символ из stdin с эхом | - | AL - ASCII-код символа |
06 | Считать символ без эха, без ожидания, без проверки на Ctrl+Break | DL = FF | - |
07 | Считать символ без эха, с ожиданием и без проверки на Ctrl+Break | - | AL - ASCII-код символа |
08 | Считать символ без эха | - | AL - ASCII-код символа |
10 (0Ah) | Считать строку с stdin в буфер | DS:DX - адрес буфера | Введённая строка помещается в буфер |
0Bh | Проверка состояния клавиатуры | - | AL=0, если клавиша не была нажата, и FF, если была |
0Ch | Очистить буфер и считать символ | AL=01, 06, 07, 08, 0Ah | - |
mov ah, 01h
int 21h ; Ввод символа, его код теперь лежит в al
mov dl, al
mov ah, 02h
int 21h ; Вывод символа, код которого лежит в dl
- 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
- загрузить регистр флагов из стека
16 битов - отдельные флажки.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
CF - PF - AF - ZF SF TF IF DF OF IOPL NT -
-
CF
(carry flag) - флаг переноса (при выходе за рязрядную сетку) -
PF
(parity flag) - флаг чётности -
AF
(auxiliary carry flag) - вспомогательный флаг переноса -
ZF
(zero flag) - флаг нуля -
SF
(sign flag) - флаг знака -
TF
(trap flag) - флаг трассировки -
IF
(interrupt enable flag) - флаг разрешения прерываний -
DF
(direction flag) - флаг направления -
OF
(overflow flag) - флаг переполнения (изменение знакового разряда) -
IOPL
(I/O privilege flag) (появился в 286 процессоре) - уровень приоритета ввода-вывода -
NT
(nested task) (появился в 286 процессоре) - флаг вложенности задач
-
STC
/CLC
/CMC
- установить/сбросить/инвертироватьCF
-
STD
/CLD
- установить/сброситьDF
(Direction Flag); Если он равен нулю (по умолчанию), то обработка строк будет идти слева направо, т.е. по возрастанию адресов памяти, иначе наоборот -
LAHF
- загрузка флагов состояния вAH
-
SAHF
- установка флагов состояния изAH
-
CLI
/STI
- запрет/разрешение прерываний (т.е. сброса/установкиIF
- Interruption Flag). Разумеется речь идёт не про программные, а про аппаратные прерывания.
- Передаёт управление в другую точку программы (на другой адрес памяти), не сохраняя какой-либо информации для возврата.
- Операнд - непосредственный адрес (вставленный в машинный код), адрес в регистре или адрес в переменной.
...
XOR AX, AX
MOV BX, 5
label1:
INC AX
ADD BX, AX
JMP label 1
- Переход типа
short
илиnear
- Обычно используются в паре с
CMP
- Термины “выше” и “ниже” - при сравнении беззнаковых чисел
- Термины “больше” и “меньше” - при сравнении чисел со знаком
Команда | Описание | Состояние флагов для выполнения перехода |
---|---|---|
JO | Есть переполнение | OF = 1 |
JNO | Нет переполнения | OF = 0 |
JS | Есть знак | SF = 1 |
JNS | Нет знака | SF = 0 |
JE, JZ | Если равно/если ноль | ZF = 1 |
JNE, JNZ | Не равно/не ноль | ZF = 0 |
JP/JPE | Есть чётность / чётное | PF = 1 |
JNP/JPO | Нет чётности / нечётное | PF = 0 |
JCXZ | CX = 0 | - |
Команда | Описание | Состояние флагов для выполнения перехода | Знаковый |
---|---|---|---|
JB JNAE JC |
Если ниже Если не выше и не равно Если перенос |
CF = 1 | Нет |
JNB JAE JNC |
Если не ниже Если выше или равно Если нет переноса |
CF = 0 | Нет |
JBE JNA |
Если ниже или равно Если не выше |
CF = 1 или ZF = 1 | Нет |
JA JNBE |
Если выше Если не ниже и не равно |
CF = 0 и ZF = 0 | Нет |
Команда | Описание | Состояние флагов для выполнения перехода | Знаковый |
---|---|---|---|
JL JNGE |
Если меньше Если не больше и не равно |
SF <> OF | Да |
JGE JNL |
Если больше или равно Если не меньше |
SF = OF | Да |
JLE JNG |
Если меньше или равно Если не больше |
ZF = 1 или SF <> OF | Да |
JG JNLE |
Если больше Если не меньше и не равно |
ZF = 0 и SF = OF | Да |
Чтобы создать многомодульную программу, нужно создать несколько файлов с исходным кодом программы. В каждом из модулей можно описывать сегменты данных/кода/стека. Каждый модулей необходимо завершить директивой END
. В главном модуле у этой директивы нужно указать в качестве параметра точку входа в программу (метку, указывающую на следующую за ней команду, с которой начнётся выполнение программы).
Для обмена данными между модулями можно использовать общий стек или директивы глобальных объявлений:
PUBLIC идентификатор
Описывает идентификатор, как доступный из других модулей.
EXTRN определение[,определение].
Указывает, что идентификатор определен в другом модуле. Определение описывает идентификатор и имеет следующий формат:
имя:тип
"Имя" - это идентификатор, который определен в другом модуле. "Тип" должен соответствовать типу идентификатора, указанному при его определении, и может быть следующим: NEAR
, FAR
, PROC
, BYTE
, WORD
, DWORD
, DATAPTR
, CODEPTR
, FWORD
, PWORD
, QWORD
, TBYTE
, ABS
или именем структуры.
- Она похожа на
PUSH
(но немного сложнее): сохраняет адрес следующей за ней команды в стеке (той, которая физически за ней стоит в памяти, а не та, которая вызывается при переходе в подпрограмму), уменьшаетSP
(в случае ближнего перехода на 2 байта, в случае дальнего - на 4 байта) и записывает по этому адресу значениеIP
либоCS:IP
, в зависимости от размера аргумента. Далее управление программы передаётся на значение операнда (в регистрIP
записывается смещение подпрограммы, а в случае дальнего перехода оно заносится ещё и вCS:IP
; насчёт дальнего перехода не уверен, там не очень чёткая формулировка была)
Адрес возврата записывается именно в стек, а не в регистры, так как если бы мы записывали его в регистры, то мы бы не смогли сделать длинную рекурсию (регистров бы не хватило).
- Передаёт управление на значение аргумента.
- Загружает из стека адрес возврата в регистр
IP
или, в случае дальнего перехода, вCS:IP
- Увеличивает
SP
(на 2/4 байта в случае ближнего/дальнего перехода)
Если указан операнд, его значение будет дополнительно прибавлено к SP
для очистки стека от параметров.
Стековый кадр (фрейм) — механизм передачи аргументов и выделения временной памяти с использованием аппаратного стека. Содержит информацию о состоянии подпрограммы.
- Параметры
- Адрес возврата (обязательно)
- Локальные переменные
MyProc proc near ; указываем тип перехода
...
Myproc endp
Возможные переходы:
-
short
- не может прыгнуть дальше -128..127 байт от команды вызова процедуры (на практике не используется, но вроде как формально возможен) -
near
- можем прыгать сюда ближним переходом (в пределах того же сегмента, метка занимает 2 байта) -
far
- можем прыгать сюда дальним переходом (в пределах всей программы, метка занимает 4 байта)
-
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, чтобы получить число с противоположным знаком.
- Неупакованное двоично-десятичное число - байт от 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
- в старший бит, при сдвиге влево всё произойдёт наоборот).
-
BTS <база>, <смещение>
- считывает во флагCF
значение из битовой строки. -
BTS <база>, <смещение>
- установить бит в 1. -
BTR <база>, <смещение>
- сбросить бит в 0. -
BTC <база>, <смещение>
- инвертировать бит. -
BSF <приёмник>, <источник>
- прямой поиск бита (от младшего разряда к старшему, т.е. справа налево). -
BSR <приёмник>, <источник>
- обратный поиск бита (от старшего разряда к младшему, т.е. слева направо). -
SETcc <приёмник>
- выставляет приёмник (1 байт) в 1 или 0 в зависимости от условия, аналогичноJcc
.
-
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
и длиной нашей строки).
Прерывание - особая ситуация, когда выполнение текущей программы приостанавливается и управление передаётся программе-обработчику возникшего прерывания.
- Аппаратные (асинхронные) - события от внешних устройств (основной вид прерываний, так как механизм прерываний в основном нужен для работы с внешними устройствами);
- Внутренние (синхронные) - события в самом процессоре, например, деление на ноль (в современных режимах работы процессоров это также различные исключения: попытка программы выйти за пределы своей памяти, обращение к несуществующему устройству и т.д.);
-
Программные - прерывания, вызванные командой
int
.
Откуда процессор берёт адреса обработчиков прерываний?
Векторы прерываний объединяются в таблицу векторов прерываний, содержащую адреса обработчиков прерываний.
- Располагается в самом начале оперативной памяти, начиная с нулевого физического адреса.
- Доступно 256 прерываний (всего аппаратных, программных и внутренних).
- Каждый вектор занимает 4 байта - полный адрес (2 байта для сегментной части адреса + 2 байта для смещения).
- Размер всей таблицы - 1 Кб.
- Приостановка выполнения текущей программы
-
Сохранение в текущий стек значение регистра флагов и полного адреса возврата (адреса следующей команды) - 6 байт. Это действие аналогично команде
CALL
- Нахождение в таблице векторов прерываний программы-обработчика прерывания
-
Передача управления по адресу обработчика из таблицы векторов, т.е. значения регистров
CS
иIP
обновляются на 4 байта, которые хранятся в таблице векторов прерываний - Возвращение управления текущей программе после окончания обработки прерывания
(ведь на обычные программы они вообще-то не влияют)
При выполнении критических процессов в какой-нибудь системной программе (например, настройка ОС и самой таблицы прерываний - тогда адреса обработчиков прерываний ещё не установлены и, соответственно, процессор обратиться к ним не может) срабатывание прерывания, которое сейчас не настроено, может оказаться критическим (например, привести к краху системы или к выполнению какого-то случайно вызванного кода).
Порты ввода-вывода - механизм взаимодействия программы, выполняемой процессором, с устройствами компьютера.
Устройства могут сами о себе что-то сообщать с помощью прерываний, а если наша программа хочет что-то им сообщить, как-то настроить или какие-то данные из них получить, то для этого нужны команды IN
и OUT
.
Порт - отдельное адресное пространство (которое никак не пересекается ни с оперативной памятью, ни с постоянной), на которое отображаются внешние устройства.
Например, какое-то количество байт будет соответствовать клавиатуре, какое-то количество байт - таймеру, какое-то количество байт - жёсткому диску и т.д.
Номер порта - участок этого адресного пространства (в котором все внешние устройства объединены по отношению к процессору)
- Пример:
IN al, 61h ; читаем один байт состояния из порта ввода-вывода 61h
OR al, 3 ; выставляем младшие 2 бита в единицы
OUT 61h, al ; отправляем полученное значение обратно в этот порт (мб мы как-то повлияли на работу устройства)