Инструкция MOV
Что такое JavaScript
Если вы интересуетесь программированием вообще, и сайтостроением в частности, то вы наверняка слышали слово JavaScript. И, если вы до сих пор не узнали толком, что же это такое, то пришло время сделать это. Подробнее… |
Пожалуй, инструкция 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 и выше, так как эти операционные системы запрещают программам напрямую обращаться к “железу”, а в этом примере мы пытаемся записать данные непосредственно в видеопамять.
Ну и напоследок скажу, почему эта инструкция называется
Первые шаги в программирование
Главный вопрос начинающего программиста – с чего начать? Вроде бы есть желание, но иногда «не знаешь, как начать думать, чтобы до такого додуматься». У человека, который никогда не имел дело с информационными технологиями, даже простые вопросы могут вызвать большие трудности и отнять много времени на решение. Подробнее… |
Регистры процессора
Главная / Ассемблер / Для чайников / Введение в Ассемблер /Начиная с модели 80386 процессоры Intel предоставляют 16 основных регистров для пользовательских программ и ещё 11 регистров для работы с мультимедийными приложениями (MMX) и числами с плавающей точкой (FPU/NPX). Все команды так или иначе изменяют содержимое регистров. Как уже говорилось, обращаться к регистрам быстрее и удобнее, чем к памяти. Поэтому при программировании на языке Ассемблера регистры используются очень широко.
В этом разделе мы рассмотрим основные регистры процессоров Intel. Названия и состав/количество регистров для других процессоров могут отличаться. Итак, основные регистры процессоров Intel.
Таблица 2.1. Основные регистры процессора.
Название | Разрядность | Основное назначение |
EAX | 32 | Аккумулятор |
EBX | 32 | База |
ECX | 32 | Счётчик |
EDX | 32 | Регистр данных |
EBP | 32 | Указатель базы |
ESP | 32 | Указатель стека |
ESI | 32 | Индекс источника |
EDI | 32 | Индекс приёмника |
EFLAGS | 32 | Регистр флагов |
EIP | 32 | Указатель инструкции (команды) |
CS | 16 | Сегментный регистр |
DS | 16 | Сегментный регистр |
ES | 16 | Сегментный регистр |
FS | 16 | Сегментный регистр |
GS | 16 | Сегментный регистр |
SS | 16 | Сегментный регистр |
Регистры EAX, EBX, ECX, EDX – это регистры общего назначения. Они имеют определённое назначение (так уж сложилось исторически), однако в них можно хранить любую информацию.
Регистры EBP, ESP, ESI, EDI – это также регистры общего назначения. Они имеют уже более конкретное назначение. В них также можно хранить пользовательские данные, но делать это нужно уже более осторожно, чтобы не получить «неожиданный» результат.
Регистр флагов и сегментные регистры требуют отдельного описания и будут более подробно рассмотрены далее.
Пока для вас здесь слишком много непонятных слов, но со временем всё прояснится)))
Когда-то процессоры были 16-разрядными, и, соответственно, все их регистры были также 16-разрядными. Для совместимости со старыми программами, а также для удобства программирования некоторые регистры разделены на 2 или 4 «маленьких» регистра, у каждого из которых есть свои имена. В таблице 2.2 перечислены такие регистры.
Вот пример такого регистра.
Из этого следует, что вы можете написать в своей программе, например, такие команды:
MOV AX, 1 MOV EAX, 1Обе команды поместят в регистр AX число 1. Разница будет заключаться только в том, что вторая команда обнулит старшие разряды регистра EAX, то есть после выполнения второй команды в регистре EAX будет число 1. А первая команда оставит в старших разрядах регистра EAX старые данные. И если там были данные, отличные от нуля, то после выполнения первой команды в регистре EAX будет какое-то число, но не 1. А вот в регистре AX будет число 1. Сложно? Ну это пока… Со временем вы к таким вещам привыкните. Мы пока не говорили о разрядах (битах). Эту тему мы обсудим в разделах, посвящённых системам счисления. А сейчас пока вам достаточно знать, что нулевой разряд (бит) – это младший бит. Он крайний справа. Старший бит – крайний слева. Номер старшего бита зависит от разрядности числа/регистра. Например, в 32-разрядном регистре старшим битом является 31-й бит (потому что отсчёт начинается с 0, а не с 1).
Ниже приведён список регистров общего назначения, которые можно поделить описанным выше способом и при этом к «половинкам» и «четвертинкам» этих регистров можно обращаться в программе как к отдельному регистру.
Таблица 2.2. «Делимые» регистры..
Регистр | Старшие разряды | Имена 16-ти и 8-ми битных регистров | |
31…16 | 15…8 | 7…0 | |
EAX | … | AX | |
AH | AL | ||
EBX | … | BX | |
BH | BL | ||
ECX | … | CX | |
CH | CL | ||
EDX | … | DX | |
DH | DL | ||
ESI | … | SI | |
EDI | … | DI | |
EBP | … | BP | |
ESP | … | SP | |
EIP | … |
На этом мы закончим наше краткое знакомство с регистрами. Если вам пока не всё понятно – просто прочитайте этот раздел, чтобы более-менее представлять себе, что такое регистры. По мере приобретения новых знаний вы можете вернуться к этому разделу и уже на новом уровне воспринять эту информацию. А в следующем разделе мы коротко опишем процесс выполнения команды.
Инструкции процессоров Intel
Что такое JavaScript
Если вы интересуетесь программированием вообще, и сайтостроением в частности, то вы наверняка слышали слово JavaScript. И, если вы до сих пор не узнали толком, что же это такое, то пришло время сделать это. Подробнее… |
Как известно, программирование на Ассемблере — это написание исходных текстов, которые представляют собой набор команд (инструкций) процессора. В этом разделе публикуются подробные описания инструкций процессоров Интел и совместимых.
Я уже не раз об этом говорил, но снова повторю — вдруг кто не слышал )))
А это важно:
Каждый процессор имеет свой набор команд (инструкций)!
Поэтому, если вы изучите инструкции одного процессора, то это не значит, что вы легко сможете создавать программы на языке Ассемблера. Потому что язык Ассемблера одинаков для всех процессоров (ну почти одинаков). Однако инструкции, используемые в языке Ассемблера, могут быть (и так оно и есть) отличаться в зависимости от того, для какого процессора вы пишите программу.
Кроме того, команды с одинаковым именем могут по разному работать с разными процессорами.
Тем не менее, изучать то язык как-то надо. Поэтому обычно начинают с каких-то основ. Как правило, с изучения основных инструкций, которые очень похожи для большинства процессоров (и микроконтроллеров в том числе).
Набор базовых команд процессора
Именно с базовых инструкций лучше всего начать изучать Ассемблер. Такими базовыми командами (инструкциями) являются команды сложения, вычитания и других простых математических операций. А также команды перемещения значения из одного источника в другой (например, из области памяти в регистр).
Пример инструкций процессора Интел (8086): ADD (сложение), SUB (вычитание), MOV (перемещение) и т.п.
Базовым набором команд процессора Intel можно считать полный набор инструкций процессора 8086, у которого было 116 команд. У современных процессоров команд, конечно, намного больше (хотя это зависит от архитектуры — есть процессоры с сокращённым набором команд, где их всего несколько десятков).
Современные процессоры кроме основных команд имеют ещё и разные расширения, такие как набор команд MMX, которые предназначены для более быстрого выполнения определённых операций.
Вообще это тема очень объёмная и довольно сложная. Поэтому в очередной раз советую вам изучить (причём очень тщательно) какую-нибудь хорошую книгу по Ассемблеру (на этом сайте есть ссылки на такие книги).
Ну а я на этом краткий обзор закончу. Смотрите содержание раздела выше. Описания новых инструкций будут периодически добавляться по мере создания материала. Так что подписывайтесь на новости сайта, чтобы всегда быть в курсе последних событий.
Первые шаги в программирование
Главный вопрос начинающего программиста – с чего начать? Вроде бы есть желание, но иногда «не знаешь, как начать думать, чтобы до такого додуматься». У человека, который никогда не имел дело с информационными технологиями, даже простые вопросы могут вызвать большие трудности и отнять много времени на решение. Подробнее… |
Первая команда в ассемблере MOV
Эта команда используется для копирования значения из приёмника в источник. Синтаксис команды:
Mov приемник, источник
Пример:
Mov edx, ecx ; правильно
Размер источника и приемника должны быть одинаковыми.
Mov al, ecx; не правильно
Этот код пытается поместить DWORD (32-битное) значение в байт (8 битов).
Правильные команды:
Mov al, bl
Mov cl, dl
Mov cx, dx
Mov ecx, ebx
Можно получить значение из памяти и поместить его в регистр. Например, имеем следующую схему памяти:
смещение | 3A | 3B | 3C | 3D | 3E | 3F | |||||||||
данные | 0D | 0A | 7A | 5E | EF | 7D | FF | AD | C7 |
Данные, которые имеют смещение 3A: 25, 7A, 5E, 72, EF, и т.д. Чтобы поместить значение со смещения 3A, например, в регистр, можно воспользоваться командой mov:
mov eax, dword ptr [0000003Ah] ; eax=725E7A25h
При работе с памятью самый младший байт сохраняется в наиболее значимом байте: порядок байтов задом на перед.
dword (32-бит) значение 10203040 шестнадцатиричное сохраняется в памяти как: 40, 30, 20, 10
word (16-бит) значение 4050 шестнадцатиричное сохраняется в памяти как: 50, 40
Пример2:
mov cl, byte ptr [34h] ; cl получит значение 0Dh
mov dx, word ptr [3Eh] ; dx получит значение 7DEFh
Размеры данных для префикса ptr:
Byte — 1 байт
Word — 2 байта
Dword — 4 байта
Размер можно не указывать:
mov eax, [00403045h]
Пример3:
mov eax, 403045h ; eax= 00403045h
mov cx, [eax] ; CX=значение (размера word) из памяти указанной в EAX (403045)
Стековые операции — PUSH, POP.
Стек это область в памяти, на которую указывает регистр стека ESP(SP). Есть две команды, для размещения значения в стеке и извлечения его из стека: PUSH и POP. Команда PUSH размещает значение в стеке, т.е. помещает значение в ячейку памяти, на которую указывает регистр ESP, после этого значение регистра ESP уменьшается на 4. Команда POP извлекает значение из стека, т.е. извлекает значение из ячейки памяти, на которую указывает регистр ESP, после этого увеличивается значение регистра ESP на 4. Значение, помещенное в стек последним, извлекается первым.
Пример:
(1) mov ecx, 100
(2) mov eax, 200
(3) push ecx
(4) push eax
(5) xor ecx, eax
(6) add ecx, 400
(7) mov edx, ecx
(8) pop ebx
(9) pop ecx
Анализ:
1: поместить 100 в ecx
2: поместить 200 в eax
3: разместить значение из ecx (=100) в стеке (размещается первым)
4: разместить значение из eax (=200) в стеке (размещается последним)
5/6/7: выполнение операций над ecx, значение в ecx изменяется
8: извлечение значения из стека в ebx: ebx=200.
9: извлечение значения из стека в ecx: ecx=100.
Пример: Работа со стеком.
Смещение | 120A | 120B | |||||||
Значение | |||||||||
ESP |
ESP стоит в том месте, на которое он указывает)
Mov ax, 4560h
Push ax
Смещение | 120A | 120B | |||||||
Значение | |||||||||
ESP |
Mov cx, FFFFh
Push cx
Смещение | 120A | 120B | |||||||
Значение | FF | FF | |||||||
ESP |
Pop edx
Смещение | 120A | 120B | |||||||
Значение | FF | FF | |||||||
ESP |
edx = 4560FFFFh.
Пересылка данных
|
xchg <операнд1>, <операнд2>
cmovcc <приёмник><источник>
bswap <регистр 32>
Особенности команды mov:
1) нельзя осуществлять пересылку из одной области памяти в другую. При такой необходимости нужно использовать в качестве промежуточного буфера любой доступный регистр общего назначения. Пример: переслать байты из ячейки памяти fls в ячейку fld:
Data
Fls dd 947503b3h
Fld dd ?
Code
Start
—-
Mov eax, fls
Mov fld,eax
—-
End start
2) нельзя загрузить в сегментный регистр значение непосредственно из памяти. Для выполнения такой загрузки нужно использовать промежуточный объект (регистр общего назначения или стек).
3) нельзя пересылать содержимое одного сегментного регистра в другой сегментный регистр. Выполнить такую пересылку можно, используя в качестве промежуточных регистры общего назначения.
Пример: инициализировать регистр es значением регистра ds:
Mov ax,ds
Move es,ax
Можно также использовать стек и команды push и pop:
Push ds ; поместить значение регистра ds в стек
Pop es ; записать в es число из стека
Нельзя использовать сегментный регистр cs в качестве операнда назначения.
5) оператор ptr можно применять и когда требуется принудительно поменять размерность операндов. К примеру, требуется переслать значение 0ffh во второй байт поля flp:
Data
Flp dw ?
Code
start:
—-
mov byte ptr (flp+1),0ffh
—-
End start
Для двунаправленной пересылки данных применяют команду xchg. Эту же операцию можно выполнить применив последовательность из нескольких команд mov. Общий вид записи:
XCHG <операнд1>, <операнд2>
Содержимое операнда2 копируется в операнд1, а старое содержимое операнда1 — в операнд2. XCHG можно выполнять над двумя регистрами или над регистром и переменной.
Например:
Xchg eax,ebx ; обменять содержимое регистров eax и ebx.
То же, что три команды на языке С:
temp = eax;
eax = ebx;
ebx = temp;
Xchg al,al ; а эта команда не делает ничего
xchg ax, word ptr [si] ; обменять содержимое регистра ах и слова в памяти по адресу в [si].
Для условной пересылки данных используется команда:
CMOVcc <приёмник><источник>
Набор команд, которые копируют содержимое источника в приемник, если удовлетворяется то или иное условие.
Можно использовать команды CMOVcc сразу после команды СМР (сравнение) с теми же операндами, например:
Читайте также:
ASSEMBLER&WIN32. КУРС МОЛОДОГО БОЙЦА. УРОК 3.mov — команда ассемблера
Когда вы пишете программу на ассемблере, вы просто пишете команды процессору. Команды процессору — это просто коды или коды операций или опкоды. Опкоды — фактически «читаемый текст»- версии шестнадцатеричных кодов. Из-за этого, ассемблер считается самым низкоуровневым языком программирования, все в ассемблере непосредственно преобразовывается в шестнадцатеричные коды. Другими словами, у вас нет компилятора, который преобразовывает язык высокого уровня в язык низкого уровня, ассемблер только преобразовывает коды ассемблера в данные.
В этом уроке мы обсудим несколько опкодов, которые имеют отношение к вычислению, поразрядным операциям, и т.д. Другие опкоды: команды перехода, сравнения и т.д, будут обсуждены позже.
Комментарии в ваших программах оставляются после точки с запятой. Точно также как в дельфи или си через //.
Числа в ассемблере могут представляться в двоичной, десятеричной или шестнадцатеричной системе. Для того, чтобы показать в какой системе использовано число надо поставить после числа букву. Для бинарной системы пишется буква b (пример: 0000010b, 001011010b), для десятеричной системы можно ничего не указывать после числа или указать букву d (примеры: 4589, 2356d), для шестнадцатеричной системы надо указывать букву h, шестнадцатеричное число надо обязательно писать с нулём в начале (примеры: 00889h, 0AC45h, 056Fh, неправильно F145Ch, С123h).
Самая первая команда будет хорошо всем известная MOV. Эта команда используется для копирования (не обращайте внимания на имя команды) значения из одного места в другое. Это ‘место’ может быть регистр, ячейка памяти или непосредственное значение (только как исходное значение). Синтаксис команды:
mov приемник, источник
Вы можете копировать значение из одного регистра в другой.
mov edx, ecx
Вышеприведенная команда копирует содержание ecx в edx. Размер источника и приемника должны быть одинаковыми, например: эта команда — НЕ допустима:
mov al, ecx ; не правильно
Этот опкод пытается поместить DWORD (32-битное) значение в байт (8 битов). Это не может быть сделано mov командой (для этого есть другие команды).
А эти команды правильные, потому что у них источник и приемник не отличаются по размеру:
mov al, bl
mov cl, dl
mov cx, dx
mov ecx, ebx
Вы также можете получить значение из памяти и поместить эго в регистр. Для примера возьмем следующую схему памяти:
смещение | 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]
Означает: поместить значение с размером 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
Вы, наверное, уже поняли, что префикс ptr обозначает, что надо брать из памяти некоторый размер. А префикс перед ptr обозначает размер данных:
Byte — 1 байт
Word — 2 байта
Dword — 4 байта
Иногда размер можно не указывать:
mov eax, [00403045h]
Так как eax — 32-разрядный регистр, ассемблер понимает, что ему также требуется 32-разрядное значение, в данном случае из памяти со смещением 403045h.
Можно также непосредственные значения:
mov edx, 5006h
Эта команда просто запишет в регистр 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.
Стековые операции — PUSH, POP . Перед тем, как рассказать вам о стековых операциях, я уже объяснял вам, что такое стек. Стек это область в памяти, на которую указывает регистр стека ESP. Стек это место для хранения адресов возврата и временных значений. Есть две команды, для размещения значения в стеке и извлечения его из стека: PUSH и POP. Команда PUSH размещает значение в стеке, т.е. помещает значение в ячейку памяти, на которую указывает регистр ESP, после этого значение регистра ESP увеличивается на 4. Команда Pop извлекает значение из стека, т.е. извлекает значение из ячейки памяти, на которую указывает регистр ESP, после этого уменьшает значение регистра ESP на 4. Значение, помещенное в стек последним, извлекается первым. При помещении значения в стек, указатель стека уменьшается, а при извлечении — увеличивается. Рассмотрим пример:
(1) mov ecx, 100
(2) mov eax, 200
(3) push ecx ; сохранение ecx
(4) push eax
(5) xor ecx, eax
(6) add ecx, 400
(7) mov edx, ecx
(8) pop ebx
(9) pop ecx
Анализ:
1: поместить 100 в ecx
2: поместить 200 в eax
3: разместить значение из ecx (=100) в стеке (размещается первым)
4: разместить значение из eax (=200) в стеке (размещается последним)
5/6/7: выполнение операций над ecx, значение в ecx изменяется
8: извлечение значения из стека в ebx: ebx станет 200 (последнее размещение, первое извлечение)
9: извлечение значения из стека в ecx: ecx снова станет 100 (первое размещение, последнее извлечение)
Чтобы узнать, что происходит в памяти, при размещении и извлечении значений в стеке, см. на рисунок ниже:
Смещение | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 120A | 120B |
Значение | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
ESP |
(стек здесь заполнен нулями, но в действительности это не так, как здесь). ESP стоит в том месте, на которое он указывает)
mov ax, 4560h
push ax
Смещение | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 120A | 120B |
Значение | 00 | 00 | 60 | 45 | 00 | 00 | 00 | 00 | 00 |
ESP |
mov cx, FFFFh
push cx
Смещение | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 120A | 120B |
Значение | FF | FF | 60 | 45 | 00 | 00 | 00 | 00 | 00 |
ESP |
pop edx
Смещение | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 120A | 120B |
Значение | FF | FF | 60 | 45 | 00 | 00 | 00 | 00 | 00 |
ESP |
edx теперь 4560FFFFh.
Вызов подпрограмм возврат из них — CALL, RET. Команда call передает управление ближней или дальней процедуре с запоминанием в стеке адреса точки возврата. Команда ret возвращает управление из процедуры вызывающей программе, адрес возврата получает из стека. Пример:
..code..
call 0455659
..more code..
Код с адреса 455659:
add eax, 500
mul eax, edx
ret
Когда выполняется команда call, процессор передает управление на код с адреса 455659, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой. Вы можете поместить код, который вы часто используете в процедуру и каждый раз когда он вам нужен вызывать его командой call.
Подробнее: команда call помещает регистр EIP (указатель на следующюю команду, которая должна быть выполнена) в стек, а команда ret извлекает его и передаёт управление этому адресу. Вы также можете определить аргументы, для вызываемой программы (процедуры). Это можно сделать через стек:
push значение_1
push значение_2
call procedure
Внутри процедуры, аргументы могут быть прочитаны из стека и использованы. Локальные переменные, т.е. данные, которые необходимы только внутри процедуры, также могут быть сохранены в стеке. Я не буду подробно рассказывать об этом, потому, что это может быть легко сделано в ассемблерах MASM и TASM. Просто запомните, что вы можете делать процедуры и что они могут использовать параметры.
Одно важное замечание:
регистр eax почти всегда используется для хранения результата процедуры.
Это также применимо к функциям windows. Конечно, вы можете использовать любой другой регистр в ваших собственных процедурах, но это стандарт.
Постигаем Си глубже, используя ассемблер / Хабр
Вдохновением послужила эта статья: Разбираемся в С, изучая ассемблер. Продолжение так и не вышло, хотя тема интересная. Многие бы хотели писать код и понимать, как он работает. Поэтому я запущу цикл статей о том, как выглядит Си-код после декомпиляции, попутно разбирая основные структуры кода.От читающих потребуются хотя бы базовые знания в следующих вещах:
- регистры процессора
- стек
- представление чисел в компьютере
- синтаксис ассемблера и Си
Но если у вас их нет, а тема вам интересна, то все это можно быстро загуглить в процессе чтения статьи. Статья не рассчитана совсем уж на новичков, но я старательно разжевывал многие простые вещи, чтобы новичкам было от чего отталкиваться.
Что будем использовать?
- Нам понадобится компилятор Си, который поддерживает современный стандарт. Можно воспользоваться онлайн компилятором на сайте ideone.com.
- Так же нам нужен декомпилятор, опять же, можно воспользоваться онлайн декомпилятором на сайте godbolt.org.
- Можно так же взять компилятор для ассемблера, который есть на ideone по ссылке выше.
Почему у нас все онлайн? Потому что это удобно для разрешения спорных ситуаций из-за различных версий и операционных систем. Компиляторов много, декомпиляторов так же хватает, не хотелось бы в дискуссии учитывать особенности каждого.
При более основательном подходе к изучению, лучше пользоваться оффлайн версиями компиляторов, можете взять связку из актуального gcc, OllyDbg и NASM. Отличия должны быть минимальны.
Простейшая программа
Эта статья не стремится повторить ту, которую я приводил в самом начале. Но начинать нужно с азов, поэтому часть материала будет вынуждено пересекаться. Надеюсь на понимание.
Первое, что нужно усвоить, компилятор даже при оптимизации нулевого уровня (-O0), может вырезать код, написанный программистом. Поэтому код следующего вида:
int main(void)
{
5 + 3;
return 0;
}
Ничем не будет отличаться от:
int main(void)
{
return 0;
}
Поэтому придется писать таким образом, чтобы при декомпиляции мы, все же, увидели превращение нашего кода во что-то осмысленное, поэтому примеры могут выглядеть, как минимум странно.
Второе, нам нужны флаги компиляции. Достаточно двух: -O0 и -m32. Этим мы задаем нулевой уровень оптимизации и 32-битный режим. С оптимизаций должно быть очевидно: нам не хочется видеть интерпретацию нашего кода в asm, а не оптимизированного. С режимом тоже должно быть очевидно: меньше регистров — больше внимания к сути. Хотя эти флаги я буду периодически менять, чтобы углубляться в материал.
Таким образом, если вы пользуетесь gcc, то компиляция может выглядеть так:
gcc source.c -O0 -m32 -o source
Соответственно, если вы пользуетесь godbolt, то вам нужно указать эти флаги в строку ввода рядом с выбором компилятора. (Первые примеры я демонстрирую на gcc 4.4.7, потом поменяю на более поздний)
Теперь, можно посмотреть первый пример:
int main(void)
{
register int a = 1; //записываем в регистровую переменную 1
return a; //возвращаем значение из регистровой переменной
}
Итак, следующий код соответствует этому:
push ebp
mov ebp, esp
push ebx
mov ebx, 1
mov eax, ebx
pop ebx
pop ebp
ret
Первые две строчки соответствую прологу функции (точнее три, но третью хочу пояснить сейчас), и мы их разберем в статье о функциях. Сейчас просто не обращайте на них внимание, тоже самое касается последних 3х строчек. Если вы не знаете asm, давайте смотреть, что означают эти команды.
Инструкции ассемблера имеют вид:
mnemonic dst, src
т. е.
инструкция получатель, источник
Тут нужно оговориться, что AT&T-синтаксис имеет другой порядок, и потом мы к нему еще вернемся, но сейчас нас интересует синтаксис схожий с NASM.
Начнем с инструкции mov. Эта инструкция перемещает из памяти в регистры или из регистров в память. В нашем случае она перемещает число 1 в регистр ebx.
Давайте кратко о регистрах: в архитектуре x86 восемь 32х битных регистров общего назначения, это значит, что эти регистры могут быть использованы программистом (в нашем случае компилятором) при написании программ. Регистры ebp, esp, esi и edi компилятор будет использовать в особых случаях, которые мы рассмотрим позже, а регистры eax, ebx, ecx и edx компилятор будет использовать для всех остальных нужд.
Таким образом mov ebx, 1, прямо соответствует строке register int a = 1;
И означает, что в регистр ebx было перемещено значение 1.
А строчка mov eax, ebx, будет означать, что в регистр eax будет перемещено значение из регистра ebx.
Есть еще две строчки push ebx и pop ebx. Если вы знакомы с понятием «стек», то догадываетесь, что сначала компилятор поместил ebx в стек, тем самым запомнил старое значение регистра, а после окончания работы программы, вернул из стека это значение обратно в регистр ebx.
Почему компилятор помещает значение 1 из регистра ebx в eax? Это связано с соглашением о вызовах функций языка Си. Там несколько пунктов, все они нас сейчас не интересуют. Важно то, что результат возвращается в eax, если это возможно. Таким образом понятно, почему единица в итоге оказывается в eax.
Но теперь логичный вопрос, а зачем понадобился ebx? Почему нельзя было написать сразу mov eax, 1? Все дело в уровне оптимизации. Я же говорил: компилятор не должен вырезать наш код, а мы написали не return 1, мы использовали регистровую переменную. Т. е. компилятор сначала поместил значение в регистр, а затем, следуя соглашению, вернул результат. Поменяйте уровень оптимизации на любой другой, и вы увидите, что регистр ebx, действительно, не нужен.
Кстати, если вы пользуетесь godbolt, то вы можете наводить мышкой на строку в Си, и вам подсветится соответствующий этой строке код в asm, при условии, что эта строка выделена цветом.
Стек
Усложним пример и перестанем пользоваться регистровыми переменными (Вы же их нечасто используете?). Посмотрим во что превратится такой код:
int main(void)
{
int a = 1; //записываем в переменную 1
int b = a + 5; //прибавим к 'a' 5 и сораним в 'b'
return b; //возвращаем значение из переменной
}
ASM:
push ebp
mov ebp, esp
sub esp, 16
mov DWORD PTR [ebp-8], 1
mov eax, DWORD PTR [ebp-8]
add eax, 5
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp-4]
leave
ret
Опять же, пропустим верхние 3 строчки и нижние 2. Теперь у нас переменная а локальная, следовательно память ей выделяется на стеке. Поэтому мы видим следующую магию: DWORD PTR [ebp-8], что же она означает? DWORD PTR — это переменная типа двойного слова. Слово — это 16 бит. Термин получил распространение в эпоху 16-ти битных процессоров, тогда в регистр помещалось ровно 16 бит. Такой объем информации стали называть словом (word). Т. е. в нашем случае dword (double word) 2*16 = 32 бита = 4 байта (обычный int).
В регистре ebp содержится адрес на вершину стека для текущей функции (мы к этому еще вернемся, потом), поэтому он смещается на 4 байта, чтобы не затереть сам адрес и дописывает значение нашей переменной. Только, в нашем случае он смещается на 8 байт для переменной a. Но если вы посмотрите на код ниже, то увидите, что переменная b лежит со смещением в 4 байта. Квадратные скобки означают адрес. Т. е. это строка работает следующим образом: на основе адреса, хранящегося в ebp, компилятор помещает значение 1 по адресу ebp-8 размера 4 байта. Почему минус восемь, а не плюс. Потому что плюсу бы соответствовали параметры, переданные в эту функцию, но опять же, обсудим это позже.
Следующая строка перемещает значение 1 в регистр eax. Думаю, это не нуждается в подробных объяснениях.
Далее у нас новая инструкция add, которая осуществляет добавление (сложение). Т. е. к значению в eax (1) добавляется 5, теперь в eax находится значение 6.
После этого нужно переместить значение 6 в переменную b, что и делается следующей строкой (переменная b находится в стеке по смещению 4).
Наконец, нам нужно вернуть значение переменной b, следовательно нужно переместить
значение в регистр eax (mov eax, DWORD PTR [ebp-4]).
Если с предыдущим все понятно, то можно переходить, к более сложному.
Интересные и не очень очевидные вещи.
Что произойдет, если мы напишем следующее: int var = 2.5;
Каждый из вас, я думаю, ответит верно, что в var будет значение 2. Но что произойдет с дробной частью? Она отбросится, проигнорируется, будет ли преобразование типа? Давайте посмотрим:
ASM:
mov DWORD PTR [ebp-4], 2
Компилятор сам отбросил дробную часть за ненадобностью.
Что произойдет, если написать так: int var = 2 + 3;
ASM:
mov DWORD PTR [ebp-4], 5
И мы узнаем, что компилятор сам способен вычислять константы. А в данном случае: так как 2 и 3 являются константами, то их сумму можно вычислить на этапе компиляции. Поэтому можно не забивать себе голову вычислением таких констант, компилятор может сделать работу за вас. Например, перевод в секунды из часов можно записать, как hours * 60 * 60. Но скорее, в пример тут стоит поставить операции над константами, которые объявлены в коде.
Что произойдет, если напишем такой код:
int a = 1;
int b = a * 2;
mov DWORD PTR [ebp-8], 1
mov eax, DWORD PTR [ebp-8]
add eax, eax
mov DWORD PTR [ebp-4], eax
Интересно, не правда ли? Компилятор решил не пользоваться операцией умножения, а просто сложил два числа, что и есть — умножить на 2. (Я уже не буду подробно описывать эти строки, вы должны понять их, исходя из предыдущего материала)
Вы могли слышать, что операция «умножение» выполняется дольше, чем операция «сложение». Именно по этим соображениям компилятор оптимизирует такие простые вещи.
Но усложним ему задачу и напишем так:
int a = 1;
int b = a * 3;
ASM
mov DWORD PTR [ebp-8], 1
mov edx, DWORD PTR [ebp-8]
mov eax, edx
add eax, eax
add eax, edx
mov DWORD PTR [ebp-4], eax
Пусть вас не вводит в заблуждение использование нового регистра edx, он ничем не хуже eax или ebx. Может понадобиться время, но вы должны увидеть, что единица попадает в регистр edx, затем в регистр eax, после чего значение eax складывается само с собой и после уже добавляется еще одна единица из edx. Таким образом, мы получили 1+1+1.
Знаете, бесконечно он так делать не будет, уже на *4, компилятор выдаст следующее:
mov DWORD PTR [ebp-8], 1
mov eax, DWORD PTR [ebp-8]
sal eax, 2
mov DWORD PTR [ebp-4], eax
mov eax, 0
Итак, у нас новая инструкция sal, что же она делает? Это двоичный сдвиг влево. Эквивалентно следующему коду в Си:
int a = 1;
int b = a << 2;
Для тех, кто не очень понимает, как работает этот оператор:
0001 сдвигаем влево (или добавляем справа) на два нуля: 0100 (т. е. 4 в 10ой системе счисления). По своей сути сдвиг влево на 2 разряда — это умножение на 4.
Забавно, что если вы умножите на 5, то компилятор сделает один sal и один add, можете сами потестировать разные числа.
На 22, компилятор на godbolt.org сдается и использует умножение, но до этого числа он пытается выкрутиться самыми разными способами. Даже вычитание использует и еще некоторые инструкции, которые мы еще не обсуждали.
Ладно, это были цветочки, а что вы думаете по поводу следующего кода:
int a = 2;
int b = a / 2;
Если вы ожидаете вычитания, то увы — нет. Компилятор будет выдавать более изощренные методы. Операция «деление» еще медленнее умножения, поэтому компилятор будет также выкручиваться:
mov DWORD PTR [ebp-4], 2
mov eax, DWORD PTR [ebp-4]
mov edx, eax
shr edx, 31
add eax, edx
sar eax
mov DWORD PTR [ebp-8], eax
Следует сказать, что для этого кода я выбрал компилятор существенно более поздней версии (gcc 7.2), до этого я приводил в пример gcc 4.4.7. Для ранних примеров существенных отличий не было, для этого примера они используют разные инструкции в 5ой строчке кода. И пример, сгенерированный 7.2, мне сейчас легче вам объяснить.
Стоит обратить внимание, что теперь переменная a находится в стеке по смещению 4, а не 8 и сразу же забыть об этом незначительном отличии. Ключевые моменты начинаются с mov edx, eax. Но пока пропустим значение этой строки. Инструкция shr осуществляет двоичный сдвиг вправо (т. е. деление на 2, если бы было shr edx, 1). И тут некоторые смогут подумать, а почему, действительно, не написать shr edx, 1, это же то, что делает код в Си? Но не все так просто.
Давайте проведем небольшую оптимизацию и посмотрим на что это повлияет. В действительности, мы нашим кодом выполняем целочисленное деление. Так как переменная «a» является целочисленным типом и 2 константа типа int, то результат никак не может получиться дробным по логике Си. И это хорошо, так как делить целочисленные числа быстрее и проще, но у нас знаковые числа, а это значит, что отрицательное число при делении инструкцией shr может отличаться на единицу от правильного ответа. (Это все из-за того, что 0 влезает по середине диапазона для знаковых типов). Если мы заменим знаковое деление на unsigned:
unsigned int a = 2;
unsigned int b = a / 2;
То получим ожидаемое. Стоит учесть, что godbolt опустит единицу в инструкции shr, и это не скомпилируется в NASM, но она там подразумевается. Измените 2 на 4, и вы увидите второй операнд в виде 2.
Теперь посмотрим на предыдущий код. В нем мы видим sar eax, это то же самое, что и shr, только для знаковых чисел. Остальной же код просто учитывает эту единицу, когда мы делим отрицательное число (или на отрицательное число, хотя код немного изменится). Если вы знаете, как представляются отрицательные числа в компьютере, вам будет не трудно догадаться, почему мы делаем сдвиг вправо на 31 разряд и добавляем это значение к исходному числу.
С делением на большие числа, все еще проще. Там деление заменяется на умножение, в качестве второго операнда вычисляется константа. Если вам будет интересно как, можете поломать над этим голову самостоятельно, там нет ничего сложного. Нужно просто понимать, как представляются вещественные числа в памяти.
Заключение
Для первой статьи материала уже больше, чем достаточно. Пора закруглятся и подводить итоги. Мы ознакомились с базовым синтаксисом ассемблера, выяснили, что компилятор может брать на себя простейшие оптимизации при вычислениях. Увидели разницу между регистровыми и стековыми переменными. И некоторые другие вещи. Это была вводная статья, пришлось много времени уделять очевидным вещам, но они очевидны не для всех, в будущем мы постигнем больше тонкостей языка Си.
Часть 2
«-» -,,,.. . «МОВ» 15 — -.
— — DOC — — — — — — — |
1 | 2 1 -. (26.11.2001 — 32 Кб) 2 -. (26.11.2001 — 32 Кб) 3 — HelloWord. (26.11.2001 — 8 Кб) 4 -. (26.11.2001 — 2 Кб) 5 -. (26.11.2001 — 2 Кб) 6 -. (27.11.2001 — 4 Кб) 7 — МОВ. (27.11.2001 — 7 Кб) 8 — ДС. (27.11.2001 — 9 Кб) 9 -. (27.11.2001 — 13 Кб) 10 — Turbo Debugger DOS. (27.11.2001 — 35 Кб) 11 — JMP. (28.11.2001 — 12 Кб) 12 -. (28.11.2001 — 14 Кб) 13 -. (28.11.2001 — 7 Кб) 14 -. (28.11.2001 — 10 Кб) 15 — IOCTL. (28.11.2001 — 5 Кб) 16 — 09ч. (29.11.2001 — 6 Кб) 17 — CMP. (29.11.2001 — 11 Кб) 18 — JE. (29.11.2001 — 9 Кб) 19 — 44H 08H INT 21H. (29.11.2001 — 8 Кб) 20 -. (30.11.2001 — 7 Кб) 21 — 25ч. (30.11.2001 — 18 Кб) 22 — ТЕСТ. (05.12.2001 — 6 Кб) 23 — Турбо-отладчик. (05.12.2001 — 20 Кб) 24 — (INT 21h 01H). (05.12.2001 — 7 Кб) 25 -. (05.12.2001 — 12 Кб) 26 — Основная загрузочная запись. (11.12.2001 — 11 Кб) 27 -. (23.12.2001 — 5 Кб) 28 -. (23.12.2001 — 2 Кб) 29 — ASCII. (23.12.2001 — 2 Кб) 30 — ВКЛЮЧИТЬ. (27.12.2001 — 3 Кб) 31 -. (27.12.2001 — 19 Кб) 32 — -. (31.12.2001 — 6 Кб) 33 — XCHG. (31.12.2001 — 12 Кб) 34 — EXE ASM. (31.12.2001 — 25 Кб) 35 -. (02.01.2002 — 14 Кб) 36 — 02ч. (02.01.2002 — 8 Кб) 37 -. (02.01.2002 — 8 Кб) 38 -. (02.01.2002 — 5 Кб) 39 -. (02.01.2002 — 5 Кб) 40 — 3dh -. (05.02.2002 — 2 Кб) 41 -. (05.02.2002 — 6 Кб) 42 -. (05.02.2002 — 2 Кб) 43 — 3ех. (05.02.2002 — 2 Кб) 44 — 3fh. (05.02.2002 — 8 Кб) 45 -. (07.01.2002 — 8 Кб) 46 — LOOP. (07.01.2002 — 8 Кб) 47 — ЛОДСБ. (08.01.2002 — 8 Кб) 48 -. (08.01.2002 — 8 Кб) 49 -. (09.01.2002 — 24 Кб) 50 -. (09.01.2002 — 10 Кб) 1 | 2 |
ОСНОВЫ СБОРКИ РЫЧАГА — MOV AX, BX
# | Псевдоним | Цель |
---|---|---|
R0 | – | Общего назначения |
R1 | – | Общего назначения |
R2 | – | Общего назначения |
R3 | – | Общего назначения |
R4 | – | Общего назначения |
R5 | – | Общего назначения |
R6 | – | Общего назначения |
R7 | – | Поддерживает номер системного вызова |
R8 | – | Общего назначения |
R9 | – | Общего назначения |
R10 | – | Общего назначения |
R11 | FP | Указатель кадра |
Регистры специального назначения | ||
R12 | IP | Внутренний процедурный звонок |
R13 | SP | Указатель стека |
R14 | LR | Регистр ссылок |
R15 | ПК | Счетчик программ |
CPSR | – | Регистр текущего состояния программы |
Следующая таблица представляет собой лишь краткий обзор того, как регистры ARM могут соотноситься с регистрами в процессорах Intel.
АРМ | Описание | x86 |
---|---|---|
R0 | Общего назначения | EAX |
R1-R5 | Общего назначения | EBX, ECX, EDX, ESI, EDI |
R6-R10 | Общего назначения | – |
R11 (FP) | Указатель кадра | EBP |
R12 | Внутренний процедурный звонок | – |
R13 (SP) | Указатель стека | ESP |
R14 (LR) | Регистр ссылок | – |
R15 (ПК) | <- Счетчик программ / Указатель команд -> | EIP |
CPSR | Текущий регистр состояния программы / флаги | EFLAGS |
R0-R12 : может использоваться во время общих операций для хранения временных значений, указателей (мест в памяти) и т. Д.R0, например, может называться аккумулятором во время арифметических операций или для хранения результата ранее вызванной функции. R7 становится полезным при работе с системными вызовами, поскольку он хранит номер системного вызова, а R11 помогает нам отслеживать границы в стеке, выступая в качестве указателя кадра (будет рассмотрено позже). Более того, соглашение о вызове функций в ARM указывает, что первые четыре аргумента функции хранятся в регистрах r0-r3.
R13: SP (указатель стека).Указатель стека указывает на верхнюю часть стека. Стек — это область памяти, используемая для хранения, специфичного для функции, которая освобождается при возврате функции. Таким образом, указатель стека используется для выделения места в стеке путем вычитания значения (в байтах), которое мы хотим выделить, из указателя стека. Другими словами, если мы хотим выделить 32-битное значение, мы вычитаем 4 из указателя стека.
R14: LR (регистр ссылок). Когда выполняется вызов функции, регистр связи обновляется адресом памяти, указывающим на следующую инструкцию, из которой была инициирована функция.Это позволяет программе вернуться к «родительской» функции, которая инициировала вызов «дочерней» функции после завершения «дочерней» функции.
R15: PC (счетчик программ). Программный счетчик автоматически увеличивается на размер выполняемой инструкции. Этот размер всегда составляет 4 байта в состоянии ARM и 2 байта в режиме THUMB. Когда выполняется инструкция ветвления, ПК сохраняет адрес назначения. Во время выполнения ПК сохраняет адрес текущей инструкции плюс 8 (две инструкции ARM) в состоянии ARM, а текущая инструкция плюс 4 (две инструкции Thumb) в состоянии Thumb (v1).Это отличается от x86, где ПК всегда указывает на следующую инструкцию, которую нужно выполнить.
Давайте посмотрим, как ПК ведет себя в отладчике. Мы используем следующую программу для сохранения адреса компьютера в r0 и включения двух случайных инструкций. Давай посмотрим что происходит.
. Раздел. Текст .global _start _Начало: mov r0, pc mov r1, # 2 добавить r2, r1, r1 bkpt
В GDB мы устанавливаем точку останова на _start и запускаем ее:
gef> br _start Точка останова 1 на 0x8054 gef> run
Вот скриншот результата, который мы видим первым:
$ r0 0x00000000 $ r1 0x00000000 $ r2 0x00000000 $ r3 0x00000000 $ r4 0x00000000 $ r5 0x00000000 $ r6 0x00000000 $ r7 0x00000000 $ r8 0x00000000 $ r9 0x00000000 $ r10 0x00000000 $ r11 0x00000000 $ r12 0x00000000 $ sp 0xbefff7e0 $ lr 0x00000000 $ pc 0x00008054 $ cpsr 0x00000010 0x8054 <_start> mov r0, pc <- $ pc 0x8058 <_start + 4> mov r0, # 2 0x805c <_start + 8> добавить r1, r0, r0 0x8060 <_start + 12> bkpt 0x0000 0x8064 и r1, r0, r1, asr # 10 0x8068 cmnvs r5, r0, lsl # 2 0x806c tsteq r0, r2, ror # 18 0x8070 и r0, r0, r11 0x8074 tsteq r8, r6, lsl # 6
Мы видим, что ПК содержит адрес (0x8054) следующей инструкции (mov r0, pc), которая будет выполнена.Теперь давайте выполним следующую инструкцию, после которой R0 должен содержать адрес ПК (0x8054), верно?
$ r0 0x0000805c $ r1 0x00000000 $ r2 0x00000000 $ r3 0x00000000 $ r4 0x00000000 $ r5 0x00000000 $ r6 0x00000000 $ r7 0x00000000 $ r8 0x00000000 $ r9 0x00000000 $ r10 0x00000000 $ r11 0x00000000 $ r12 0x00000000 $ sp 0xbefff7e0 $ lr 0x00000000 $ pc 0x00008058 $ cpsr 0x00000010 0x8058 <_start + 4> mov r0, # 2 <- $ pc 0x805c <_start + 8> добавить r1, r0, r0 0x8060 <_start + 12> bkpt 0x0000 0x8064 и r1, r0, r1, asr # 10 0x8068 cmnvs r5, r0, lsl # 2 0x806c tsteq r0, r2, ror # 18 0x8070 и r0, r0, r11 0x8074 tsteq r8, r6, lsl # 6 0x8078 adfcssp f0, f0, # 4.0
… верно? Неправильно. Посмотрите на адрес в R0. Хотя мы ожидали, что R0 будет содержать ранее прочитанное значение ПК (0x8054), вместо этого он содержит значение, которое на две инструкции опережает значение ПК, которое мы ранее считали (0x805c). Из этого примера вы можете видеть, что когда мы напрямую читаем PC, это следует из определения, что PC указывает на следующую инструкцию; но при отладке ПК указывает на две инструкции впереди текущего значения ПК (0x8054 + 8 = 0x805C). Это связано с тем, что старые процессоры ARM всегда выбирали на две инструкции раньше выполняемых в данный момент инструкций.ARM сохраняет это определение для обеспечения совместимости с более ранними процессорами.
ADD | ADD R0, R1, Operand2 ADD R0, R1, 12 бит конст. | R0 = R1 + R2 | N, Z, C, V | S | |
АЦП | АЦП R0, R1, R2 АЦП R0, R1, 8 бит, константа | R0 = R1 + R2 + C | N, Z, C, V | S | |
ADDW | ADD R0, R1, 12 бит конст. | 12 бит | N, Z, C, V | ||
ПОД | SUB R0, R1, R2 SUB R0, R1, 12 бит конст. | R0 = R1 — R2 | N, Z, C, V | S | |
SBC | SBC R0, R1, R2 SBC R0, R1, 8 бит, конст. | R0 = R1 — R2 — C | N, Z, C, V | S | |
SUBW | SUB R0, R1, 12 бит, конст. | 12 бит | N, Z, C, V | ||
RSB | RSB R0, R1, R2 RSB R0, R1, 8 бит конст. RSB R0, R1, R2, ASR № 23 | . R0 = R2 — R1 R0 = 8 бит const — R1 | N, Z, C, V | S | |
ADR | ADR R0, этикетка +/- 12 бит ADR.W R0, этикетка +/- 32 бит | . | |||
LDR STR | LDR R0, [R1, # 8bit const.]! — LDR R0, [R1], # 8бит конст. — LDRB R0, [R1] — STRB R0, [R1], # 1 | / . B =, SB = () H =, SH = () | |||
LDR STR | LDR R0, [R1, R2, {LSL # 0..3}] STR R0, [R1, R2, {LSL # 0..3}] | . B, SB, H, SH | |||
LDR LDRD | LDR R0, этикетка LDRD R0, R1, этикетка | . B, SB, H, SH STR / STRD. | |||
LDRT STRT | . LDR / STR. | ||||
LDRD STRD | LDRD R0, R1, [R2, # 10bit const.]! — LDRD R0, R1, [R2], # 10бит конст. — LDRD R0, R1, [R2] — STRD R0, R1, [R2] | / . 4. | |||
LDM STM | LDM R0, {R1-R3} LDM R0 !, {R1-R3} — R0 IA, DB, FD, EA -. | /. IA — ДБ -. | |||
PUSH POP | НАЖАТЬ {R0, R2-R7, R12} POP {R0, R2-R7, R12} | / | |||
LDREX STREX | LDREX R1, [R2, # 10бит конст.] STREX R0, R1, [R2, # 10bit const.] | /. B =, H =. | |||
CLREX | CLREX () | . |