Коды операции
Win32Asm Tutorial |
назад | 5- Коды рперации | вперед |
5.0 - Коды операции (опкоды).
Опкоды это команды для процессора. Опкоды - фактически "читаемый текст"- версии шестнадцатиричных кодов. Из-за этого, ассемблер считается саммым низко-уровневым языком программирования, все в ассемблере непосредственно преобразовывается в шестнадцатиричные коды. Другими словами, у вас нет компилятора, который преобразовывает язык высокого уровня в язык низкого уровня, ассемблер только преобразовывает коды ассемблера в данные.
В этом уроке мы обсудим несколько опкодов, которые имеют отношение к вычислению, поразрядным операциям, и т.д. Другие опкоды, команды перехода, сравнения и т.д, будут обсуждены позже.
5.1 - Несколько основных опкодов вычисления
MOV |
Эта команда используется для перемещения (или фактически, копирования) значения из одного места в другое. Это 'место' может быть регистр, ячейка памяти или непосредственное значение (только как исходное значение). Синтаксис команды mov:
mov приемник, источник
Вы можете перемещать значение из одного регистра в другой (Учтите, что команда копирует значение в приемник, несмотря на ее название 'move', что в переводе на русский - перемещать).
mov edx, ecx
Вышеприведенная команда копирует содержание ecx в edx. Размер источника и приемника должны быть одинаковыми,
например: эта команда - НЕ допустима:
mov al, ecx ; не правильно
Этот опкод пытается поместить DWORD (32-битное) значение в байт (8 битов). Это не может быть сделано mov командой (для этого есть другие команды).
А эти команды правильные, потому что у них источник и приемник не отличаются по размеру:
mov al, bl
mov cl, dl
mov cx, dx
mov ecx, ebx
Смещение указывает на ячейки памяти (в win32, подробнее об этом см. на предыдущей странице). Вы также можете получить значение из памяти и поместить эго в регистр. Для примера возьмем следующую таблицу:
смещение | 34 | 35 | 36 | 37 | 38 | 39 | 3A | 3B | 3C | 3D | 3E | 3F | 40 | 41 | 42 | |||||||||||||||
данные | 0D | 0A | 50 | 32 | 44 | 57 | 25 | 7A | 5E | 72 | EF | 7D | FF | AD | C7 |
(Каждый блок представляет байт)
Значение смещения обозначено здесь как байт, но на самом деле это это - 32-разрядное значение. Возьмем для примера 3A, это также - 32-разрядное значение: 0000003Ah. Только, чтобы с экономить пространство, некоторые используют маленькие смещения. Все значения - шестнадцатиричные коды.
Посмотрите на смещение 3A в таблице выше. Данные на этом смещении - 25, 7A, 5E, 72, EF, и т.д. Чтобы поместить значение со смещения 3A, например, в регистр, вы также используете команду mov:
mov eax, dword ptr [0000003Ah]
(суффикс 'h' означает шестнадцатеричное значение)
Команда mov eax, dword ptr [0000003Ah] означает: поместить значение с размером DWORD (32-разряда) из памяти со смещением 3Ah в регистр eax. После выполнения этой команды, eax будет содержать значение 725E7A25h. Возможно вы заметили, что это - инверсия того что находится в памяти: 25 7A 5E 72. Это потому, что значения сохраняются в памяти, используя формат little endian . Это означает, что самый младший байт сохраняется в наиболее значимом байте: порядок байтов задом на перед. Я думаю, что эти примеры покажут это:
dword (32-бит) значение 10203040 шестнадцатиричное сохраняется в памяти как: 40, 30, 20, 10
word (16-бит) значение 4050 шестнадцатиричное сохраняется в памяти как: 50, 40
Вернемся к примеру выше. Вы также можете это делать и с другими размерами:
mov cl, byte ptr [34h] ; cl получит значение 0Dh (см. таблицу выше)
mov dx, word ptr [3Eh] ; dx получит значение 7DEFh (см. таблицу выше, помните о порядке байт - задом на перед)
Иногда размер можно не указывать:
mov eax, [00403045h]
Так как eax - 32-разрядный регистр, ассемблер понимает, что ему также требуется 32-разрядное значение, в данном случае из памяти со смещением 403045h.
Можно также непосредственные значения:
mov edx, 5006
Эта команда просто запишет в регистр edx, значение 5006. Скобки, [ и ], используются, для получения значения из памяти (в скобках находится смещение), без скобок, это просто непосредственное значение.
Можно также использовать регистр как ячейку памяти (он должен быть 32-разрядным в 32-разрядных программах):
mov eax, 403045h ; пишет в eax значение 403045 шестнадцатиричное.
mov cx, [eax] ; помещает в регистр CX значение (размера word) из памяти указанной в EAX (403045)
В mov cx, [eax], процессор сначала смотрит, какое значение (= ячейке памяти) содержит eax, затем какое значение находится в той ячейке памяти, и помещает это значение (word, 16 бит, потому что приемник, cx, является 16-разрядным регистром) в CX.
ADD, SUB, MUL, DIV |
Многие опкоды делают вычисления. Вы можете узнать многие из них по их названиям: add (addition - добавление), sub (substraction - вычитание), mul (multiply - умножение), div (divide - деление) и т.д.
Опкод add-имеет следующий синтаксис:
add приемник, источник
Выполняет вычисление: приемник = приемник + источник.
Имеются также другие формы:
приемник | источник | пример |
регистр | регистр | add ecx, edx |
регистр | память | add ecx, dword ptr [104h] / add ecx, [edx] |
регистр | значение | add eax, 102 |
память | значение | add dword ptr [401231h], 80 |
память | регистр | add dword ptr [401231h], edx |
Эта команда очень проста. Она добавляет значение источника к значение приемника и помещает результат в приемник.
Другие математические команды:
sub приемник, источник (приемник = приемник - источник)
mul множимое, множитель (множимое = множимое * множитель)
div делитель (eax = eax / делитель, edx = остаток)
Поскольку регистры могут содержать только целочисленные значения (то есть числа, не, с плавающей запятой), результат деления разбит на частное и остаток. Например:
28 /6 --> частное = 4, остаток = 4
30 /9 --> частное = 3, остаток = 3
97 / 10 --> частное = 9, остаток = 7
18 /6 --> частное = 3, остаток = 0
Теперь, в зависимости от размера источника, частное сохраняется в eax, а остаток в edx:
размер источника | деление | частное в... | остаток в... |
BYTE (8-bits) | ax / делитель | AL | AH |
WORD (16-bits) | dx:ax* / делитель | AX | DX |
DWORD (32-bits) | edx:eax* / делитель | EAX | EDX |
* = Например: если dx = 2030h, а ax = 0040h, dx: ax = 20300040h. Dx:ax - значение dword, где dx представляет старшее word, а ax - младшее. Edx:eax - значение quadword (64 бита), где старшее dword в edx и младшее в eax.
Источник операции деления (div) может быть:
Источник не может быть непосредственным значением, потому что тогда процессор не сможет определить размер исходного операнда.
ОПЕРАЦИИ С БИТАМИ |
Эти команды работают с приемником и источником, исключение команда 'NOT'. Каждый бит в приемнике сравнивается с тем же самым битом в источнике, и в зависимости от команды, 0 или 1 помещается в бит приемника:
команда | AND | OR | XOR | NOT | ||||||||||
Бит источника | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
Бит приемника | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | X | X |
Бит результата | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
AND устанавливает бит результата в 1, если оба бита, бит источника и бит приемника установлены в 1.
OR устанавливает бит результата в 1, если один из битов, бит источника или бит приемника установлен в 1.
XOR устанавливает бит результата в 1, если бит источника отличается от бита приемника.
NOT инвертирует бит источника.
Пример:
mov ax, 3406
mov dx, 13EAh
xor ax, dx
ax = 3406 (десятичное), в двоичном - 0000110101001110.
dx = 13EA (шестнадцатиричное), в двоичном - 0001001111101010.
Выполнение операции XOR на этими битами:
Источник | 0001001111101010 (dx) |
Приемник | 0000110101001110 (ax) |
Результат | 0001111010100101 (новое значение в ax) |
Новое значение в ax, после выполнения команды - 0001111010100101 (7845 - в десятичном, 1EA5 - в шестнадцатиричном).
Другой пример:
mov ecx, FFFF0000h
not ecx
FFFF0000 в двоичном это - 11111111111111110000000000000000
Если вы выполните инверсию каждого бита, то получите:
00000000000000001111111111111111 , в шестнадцатиричном это 0000FFFF
Значит после операции NOT, ecx будет содержать 0000FFFFh.
УВЕЛИЧЕНИЕ/УМЕНЬШЕНИЕ |
Есть 2 очень простые команды, DEC и INC. Эти команды увеличивают или уменьшают содержимое памяти или регистра на единицу. Просто поместите:
inc регистр -> регистр = регистр + 1
dec регистр -> регистр = регистр - 1
inc dword ptr [103405] -> значение в [103405] будет увеличено на 1.
dec dword ptr [103405] -> значение в [103405] будет уменьшено на 1.
NOP |
Эта команда не делает абсолютно ничего (пустая команда). Она только занимает пространство и время. Используется для резервирования места в сегменте кода или организации программной задержки
Биты, сдвиг логический, арифметический и циклический |
Обратите внимание: Большинство примеров ниже использует 8 битные числа, это сделано для того, чтобы вам было легче понять, как это работает.
Функции сдвига (сдвиг логический операнда влево/вправо)
SHL операнд, количество_сдвигов
SHR операнд, количество_сдвигов
SHL и SHR сдвигают биты операнда (регистр/память) влево или вправо соответственно на один разряд.
Указанное выше действие повторяется количество раз, равное значению второго операнда.
Пример:
; al = 01011011 (двоичное)
shr al, 3
Это означает: сдвиг всех битов регистра al на 3 разряда вправо. Так что al станет 00001011. Биты слева заполняются нулями, а биты справа выдвигаются. Последний выдвинутый бит, становится значением флага переноса cf.
Бит переноса это бит флагового регистра процессора. Этот регистр не такой как eax или ecx, к которому вы можете непосредственно обращаться (хотя есть опкоды, которые это делают), его содержание, зависит от результатов многих команд. Об этом я вам расскажу позже, единственное, что вы должны сейчас запомнить, это то, что флаг переноса это бит во флаговом регистре и что он может быть установлен (т.е. равен 1) или сброшен (равен 0).
Команда shl такая же, как и shr, но сдвигает влево.
; bl = 11100101 (двоичное)
shl bl, 2
После выполнения команды регистр bl будет равен 10010100 (двоичное). Два последних бита заполнились нулями, флаг переноса установлен, потому, что последний выдвинутый слева бит был равен 1
Здесь есть еще два других опкода: (сдвиг арифметический операнда влево/вправо)
SAL операнд, количество_сдвигов (Shift Arithmetic Left)
SAR операнд, количество_сдвигов (Shift Arithmetic Right)
Команда SAL такая же, как SHL, а вот SAR не совсем такая, как SHR. Команда SAR также, как и SHR сдвигает все биты операнда вправо на один разряд, при этом выдвигаемый справа бит становится значением флага переноса cf.
Обратите внимание: одновременно слева в операнд вдвигается не нулевой бит, как в SHR, а значение старшего бита операнда. Пример:
al = 10100110
sar al, 3
al = 11110100
sar al, 2
al = 11111101
bl = 00100110
sar bl, 3
bl = 00000010
Циклический сдвиг
rol операнд, количество_сдвигов ; циклический сдвиг операнда влево
ror операнд, количество_сдвигов ; циклический сдвиг операнда вправо
rcl операнд, количество_сдвигов ; циклический сдвиг операнда влево через флаг переноса
rcr операнд, количество_сдвигов ; циклический сдвиг операнда вправо через флаг переноса
Циклический сдвиг напоминает смещение, выдвигаемые биты, снова вдвигаются с другой стороны:
Пример: команды ror (циклический сдвиг вправо)
бит 7 |
бит 6 |
бит 5 |
бит 4 |
бит 3 |
бит 2 |
бит 1 |
бит 0 |
выдвиг- ается | ||
Операнд | 1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
||
ror операнд,3 | 1 |
0 |
0 |
1 |
1 |
0 1 1 | ||||
Результат | 0 |
1 |
1 |
1 |
0 |
0 |
1 |
1 |
Как видно из рисунка выше, биты вращаются, то есть каждый бит, который выталкивается снова вставляется с другой стороны. Флаг переноса cf содержит значение последнего выдвинутого бита.
RCL и RCR фактически такие же как ROL и ROR. Их названия предлагают, что они используют флаг переноса cf, для указания последнего выдвигаемого бита, но поскольку ROL и ROR делают тот же самое, они не отличаются от них.
Примечание от переводчика: Вот тут мне кажется автор допустил ошибку относительно команд RCR и RCL, эти команды отличаются от ROR и ROL. Здесь я опишу алгоритм работы тех и других:
RCL и RCR сдвигают все биты операнда влево (для RCL) или вправо (для RCR) на один разряд, при этом старший(для RCL) или младший(для RCR) бит становится значением флага переноса cf; одновременно старое значение флага переноса cf вдвигается в операнд справа(для RCL) или слева(для RCR) и становится значением младшего(для RCL) или старшего(для RCR) бита операнда. Указанные действия повторяются количество раз, равное значению второго операнда.
ROL и ROR сдвигают все биты операнда влево(для ROL) или вправо(для ROR) на один разряд, при этом старший(для ROL) или младший(для ROR) бит операнда вдвигается в операнд справа(для ROL) или слева(для ROR) и становится значением младшего(для ROL) или старшего(для ROR) бита операнда; одновременно выдвигаемый бит становится значением флага переноса cf. Указанные действия повторяются количество раз, равное значению второго операнда.
Обмен |
Команда XCHG также весьма проста. Назначение: обмен двух значений между регистрами или между регистрами и памятью:
eax = 237h
ecx = 978h
xchg eax, ecx
eax = 978h
ecx = 237h
[наверх]