Dword ptr ассемблер что это

от admin

2.5. Простейшие способы адресации

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

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

оба ее операнда заданы регистровым способом адресации, а в команде

левый операнда задан регистровым способом адресации, а правый — другим способом.

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

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

обеспечивают сложения значений из 4-х байтовых полей данных с именами x и y с последующим помещением результата в двойное слово области z. Практически эти команды реализуют оператор Си

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

MOV eax, [00000020]

ADD eax, [00000024]

MOV [00000028], eax

отражающие, в частности, тот факт, что именованные в исходном файле области x, y, z в сегменте данных исполняемого файла будут находиться со смещениями 20, 24 и 28 от начала этого сегмента. Именно поэтому для обозначения на ассемблере данных в памяти компьютера используются вспомогательные синтаксические элементы — квадратные скобки. Убрав скобки из последнего фрагмента команд, мы бы получили, что первая команда приказывает занести числовую константу 20 в регистр eax, а вторая команда приказывает прибавить к содержимому регистра eax значение числовой константы 24. Последние приказания совершенно отличны от задаваемых в исходном файле действий, где область данных, наименованная x, содержит число 56, а область данных, наименованная y, содержит число -37. Другое дело, что область, ранее обозначенная x, в машинном коде обозначается просто как лежащее со смещением 20 от начала сегмента данных, а область, ранее обозначенная как y, лежит со смещением 24 от начала сегмента данных. (Заметим, что в языках высокого уровня никогда не появляются действительные обозначения переменных числовыми смещениями от начала сегмента и, поэтому нет никакой необходимости вводить специальные символы, подчеркивающие, что имя обозначает место в памяти, а не само значение, как бывает для констант, размещаемых внутри кода команды.)

С точки поверхностного программирования действие команды

равносильно действию команды

где имя kkk определено в сегменте данных как

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

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

Для частичной демонстрации рассмотренных способов адресации обратимся к более содержательному примеру, представленному программой в листинге 2.5.1. Эта программа вводит любой текст, набираемый пользователем (но не более 80 символов) и затем заменяет в нем внутри программы второй из введенных символов на символ ! (восклицательный знак). Преобразованный таким образом текст выводится на экран.

; Ввод строки текста, изменение в ней второго из введенных на символ ! и

; вывод полученной строки

;— read(1, buf, 80 == <3>(ebx, ecx, edx)

mov eax,3 ; N function=read

mov ebx,0 ; 0 handle=0 (stdin)

mov ecx, buf ; address of buf

mov edx,80 ; number of byte

;— write(1, buf, [len]) == <4>(ebx, ecx, edx)

mov eax,4h ; N function=write

mov ebx,1 ; N handle=1 (stdout)

mov ecx, buf ; address of buf

mov edx,[len] ; number of byte

int 80h ; function=exit

buf times 80 db 0 ; или resb 80; но тогда будет предупреждение

Листинг 2.5.1. Простейшее использование прямой адресации

В этой программе использованы две области с именами buf и len, вторая из них предназначена для временного хранения длины введенного текста, а первая служит буфером ввода и вывода. Обращение к области (переменной) len в командах программы использует запись соответствующего операнда в виде [len], так что для доступа к этой области применяется исключительно прямой способ адресации.

Для доступа ко второму байту (считая, что начальный байт области называется первым) используется также прямой способ адресации, записываемый в операндах в виде

Если мы запишем в операнде [buf+0], то попадем на самое начало области, что равносильно записывается как [buf], для обозначения 10-го байта в этой области достаточно записать операнд в виде [buf+9]. Заметим, что в профессиональном программировании целесообразно нумеровать элементы массива и, в частности, байты области данных, начиная с нуля, так что n-й байт области данных с именем obl обозначится в операнде команды как [obl+n], где элемент n должен быть записан числом или путем использования рассмотренной выше и записанной где-то в программе директивы

n equ число

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

не может быть компилятором ассемблера NASM преобразован в машинный код, даже при правильно определенной области данных xxx (например, с помощью директивы xxx DD 0), потому что непонятно, сколько байтов двоичного кода требуется поместить в качестве значения константы -127: 8 битов, 16 битов или все 32 бита. (Если человеческие записи чисел не используют нулевых цифр перед первой значащей цифрой, то в машинных структурах совсем наоборот, более того, машинные способы записи отрицательных чисел в первом случае требуют запомнить шестнадцатеричный код 7F, во втором — FF7F, а в третьем — FFFFFF7F.) Для решения проблемы однозначной определенности в ассемблеры введены дополнительные средства. В ассемблере NASM они представляют собой модификаторы разрядности, задаваемые служебными словами BYTE, WORD и DWORD. Эти модификаторы ставятся перед теми операндами, разрядность которых необходимо уточнить. В частности, вместо недоопределенной выше команды имеется возможность для указанных вариантов задать одну из следующих команд:

MOV BYTE [xxx], -127

MOV WORD [xxx], -127

MOV DWORD [xxx], -127

В ассемблерах MASM и TASM принята другая методология использования имен данных. Согласно возвышенным принципам, заложенным в эти ассемблеры, именам данных соотносятся не только значения, задаваемые смещением начала именованных данных относительно начала сегмента, но и атрибуты. К этим атрибутам относят имя сегмента, в котором данные определены, и характеристику размера. Эти характеристики обозначаются служебными словами BYTE, WORD и DWORD (и рядом других). Поэтому в этих ассемблерах запись команды в виде

интерпретируется по-разному в зависимости от определения имени xxx. На самом деле это далеко не идеальное решение, так как, во-первых, значение имени может быть задано директивой EQU и тогда вступает далеко не явное допущение. Во-вторых, иногда возникает необходимость область данных, определенную одним размером, частично заполнить данными меньшего размера (например, младшую и старшую половину отдельными командами). Если имя области данных на самом деле именует область неоднородной — с точки зрения программиста — структуры, то использование обозначения вида [имя_области + число] естественно для доступа к части фактической структуры, но автоматически приобретает атрибут начального поля в такой структуре.

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

С учетом отмеченной необходимости и в MASM, TASM явно задавать размер операндов там, где это необходимо или при желании отказаться от решения по умолчанию, в эти ассемблеры также введены средства явного задания размера операндов. К сожалению, высокие соображения привели к более сложным конструкциям. Для задания размеров в 8, 16 и 32 бита предназначены обозначения BYTE PTR, WORD PTR и DWORD PTR, так что на этих ассемблерах описанные выше примеры запишутся как

MOV BYTE PTR [xxx], -127

MOV WORD PTR [xxx], -127

MOV DWORD PTR [xxx], -127

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

Постигаем Си глубже, используя ассемблер

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

От читающих потребуются хотя бы базовые знания в следующих вещах:

  • регистры процессора
  • стек
  • представление чисел в компьютере
  • синтаксис ассемблера и Си

Что будем использовать?

  1. Нам понадобится компилятор Си, который поддерживает современный стандарт. Можно воспользоваться онлайн компилятором на сайте ideone.com.
  2. Так же нам нужен декомпилятор, опять же, можно воспользоваться онлайн декомпилятором на сайте godbolt.org.
  3. Можно так же взять компилятор для ассемблера, который есть на ideone по ссылке выше.

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

Простейшая программа

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

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

Ничем не будет отличаться от:

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

Второе, нам нужны флаги компиляции. Достаточно двух: -O0 и -m32. Этим мы задаем нулевой уровень оптимизации и 32-битный режим. С оптимизаций должно быть очевидно: нам не хочется видеть интерпретацию нашего кода в asm, а не оптимизированного. С режимом тоже должно быть очевидно: меньше регистров — больше внимания к сути. Хотя эти флаги я буду периодически менять, чтобы углубляться в материал.

Таким образом, если вы пользуетесь gcc, то компиляция может выглядеть так:

gcc source.c -O0 -m32 -o source

Соответственно, если вы пользуетесь godbolt, то вам нужно указать эти флаги в строку ввода рядом с выбором компилятора. (Первые примеры я демонстрирую на gcc 4.4.7, потом поменяю на более поздний)

Теперь, можно посмотреть первый пример:

Итак, следующий код соответствует этому:

Первые две строчки соответствую прологу функции (точнее три, но третью хочу пояснить сейчас), и мы их разберем в статье о функциях. Сейчас просто не обращайте на них внимание, тоже самое касается последних 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, при условии, что эта строка выделена цветом.

Усложним пример и перестанем пользоваться регистровыми переменными (Вы же их нечасто используете?). Посмотрим во что превратится такой код:

Опять же, пропустим верхние 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. Но что произойдет с дробной частью? Она отбросится, проигнорируется, будет ли преобразование типа? Давайте посмотрим:

Компилятор сам отбросил дробную часть за ненадобностью.

Что произойдет, если написать так: int var = 2 + 3;

И мы узнаем, что компилятор сам способен вычислять константы. А в данном случае: так как 2 и 3 являются константами, то их сумму можно вычислить на этапе компиляции. Поэтому можно не забивать себе голову вычислением таких констант, компилятор может сделать работу за вас. Например, перевод в секунды из часов можно записать, как hours * 60 * 60. Но скорее, в пример тут стоит поставить операции над константами, которые объявлены в коде.

Что произойдет, если напишем такой код:

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

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

Но усложним ему задачу и напишем так:

Пусть вас не вводит в заблуждение использование нового регистра edx, он ничем не хуже eax или ebx. Может понадобиться время, но вы должны увидеть, что единица попадает в регистр edx, затем в регистр eax, после чего значение eax складывается само с собой и после уже добавляется еще одна единица из edx. Таким образом, мы получили 1+1+1.

Знаете, бесконечно он так делать не будет, уже на *4, компилятор выдаст следующее:

Итак, у нас новая инструкция sal, что же она делает? Это двоичный сдвиг влево. Эквивалентно следующему коду в Си:

Для тех, кто не очень понимает, как работает этот оператор:

0001 сдвигаем влево (или добавляем справа) на два нуля: 0100 (т. е. 4 в 10ой системе счисления). По своей сути сдвиг влево на 2 разряда — это умножение на 4.

Забавно, что если вы умножите на 5, то компилятор сделает один sal и один add, можете сами потестировать разные числа.

На 22, компилятор на godbolt.org сдается и использует умножение, но до этого числа он пытается выкрутиться самыми разными способами. Даже вычитание использует и еще некоторые инструкции, которые мы еще не обсуждали.

Ладно, это были цветочки, а что вы думаете по поводу следующего кода:

Если вы ожидаете вычитания, то увы — нет. Компилятор будет выдавать более изощренные методы. Операция «деление» еще медленнее умножения, поэтому компилятор будет также выкручиваться:

Следует сказать, что для этого кода я выбрал компилятор существенно более поздней версии (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:

То получим ожидаемое. Стоит учесть, что godbolt опустит единицу в инструкции shr, и это не скомпилируется в NASM, но она там подразумевается. Измените 2 на 4, и вы увидите второй операнд в виде 2.

Теперь посмотрим на предыдущий код. В нем мы видим sar eax, это то же самое, что и shr, только для знаковых чисел. Остальной же код просто учитывает эту единицу, когда мы делим отрицательное число (или на отрицательное число, хотя код немного изменится). Если вы знаете, как представляются отрицательные числа в компьютере, вам будет не трудно догадаться, почему мы делаем сдвиг вправо на 31 разряд и добавляем это значение к исходному числу.

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

Заключение

Для первой статьи материала уже больше, чем достаточно. Пора закруглятся и подводить итоги. Мы ознакомились с базовым синтаксисом ассемблера, выяснили, что компилятор может брать на себя простейшие оптимизации при вычислениях. Увидели разницу между регистровыми и стековыми переменными. И некоторые другие вещи. Это была вводная статья, пришлось много времени уделять очевидным вещам, но они очевидны не для всех, в будущем мы постигнем больше тонкостей языка Си.

x86 Assembly Guide

This guide describes the basics of 32-bit x86 assembly language programming, covering a small but useful subset of the available instructions and assembler directives. There are several different assembly languages for generating x86 machine code. The one we will use in CS216 is the Microsoft Macro Assembler (MASM) assembler. MASM uses the standard Intel syntax for writing x86 assembly code.

The full x86 instruction set is large and complex (Intel’s x86 instruction set manuals comprise over 2900 pages), and we do not cover it all in this guide. For example, there is a 16-bit subset of the x86 instruction set. Using the 16-bit programming model can be quite complex. It has a segmented memory model, more restrictions on register usage, and so on. In this guide, we will limit our attention to more modern aspects of x86 programming, and delve into the instruction set only in enough detail to get a basic feel for x86 programming.

Resources

  • Guide to Using Assembly in Visual Studio — a tutorial on building and debugging assembly code in Visual Studio
  • Intel x86 Instruction Set Reference
  • Intel’s Pentium Manuals (the full gory details)

Registers

Modern (i.e 386 and beyond) x86 processors have eight 32-bit general purpose registers, as depicted in Figure 1. The register names are mostly historical. For example, EAX used to be called the accumulator since it was used by a number of arithmetic operations, and ECX was known as the counter since it was used to hold a loop index. Whereas most of the registers have lost their special purposes in the modern instruction set, by convention, two are reserved for special purposes — the stack pointer (ESP) and the base pointer (EBP).

For the EAX, EBX, ECX, and EDX registers, subsections may be used. For example, the least significant 2 bytes of EAX can be treated as a 16-bit register called AX. The least significant byte of AX can be used as a single 8-bit register called AL, while the most significant byte of AX can be used as a single 8-bit register called AH. These names refer to the same physical register. When a two-byte quantity is placed into DX, the update affects the value of DH, DL, and EDX. These sub-registers are mainly hold-overs from older, 16-bit versions of the instruction set. However, they are sometimes convenient when dealing with data that are smaller than 32-bits (e.g. 1-byte ASCII characters).

When referring to registers in assembly language, the names are not case-sensitive. For example, the names EAX and eax refer to the same register.


Figure 1. x86 Registers

Memory and Addressing Modes

Declaring Static Data Regions
.DATA
var DB 64 ; Declare a byte, referred to as location var, containing the value 64.
var2 DB ? ; Declare an uninitialized byte, referred to as location var2.
DB 10 ; Declare a byte with no label, containing the value 10. Its location is var2 + 1.
X DW ? ; Declare a 2-byte uninitialized value, referred to as location X.
Y DD 30000 ; Declare a 4-byte value, referred to as location Y, initialized to 30000.

Unlike in high level languages where arrays can have many dimensions and are accessed by indices, arrays in x86 assembly language are simply a number of cells located contiguously in memory. An array can be declared by just listing the values, as in the first example below. Two other common methods used for declaring arrays of data are the DUP directive and the use of string literals. The DUP directive tells the assembler to duplicate an expression a given number of times. For example, 4 DUP(2) is equivalent to 2, 2, 2, 2 .

Z DD 1, 2, 3 ; Declare three 4-byte values, initialized to 1, 2, and 3. The value of location Z + 8 will be 3.
bytes DB 10 DUP(?) ; Declare 10 uninitialized bytes starting at location bytes.
arr DD 100 DUP(0) ; Declare 100 4-byte words starting at location arr , all initialized to 0
str DB ‘hello’,0 ; Declare 6 bytes starting at the address str, initialized to the ASCII character values for hello and the null ( 0 ) byte.
Addressing Memory

The addressing modes can be used with many x86 instructions (we’ll describe them in the next section). Here we illustrate some examples using the mov instruction that moves data between registers and memory. This instruction has two operands: the first is the destination and the second specifies the source.

Some examples of mov instructions using address computations are:

mov eax, [ebx] ; Move the 4 bytes in memory at the address contained in EBX into EAX
mov [var], ebx ; Move the contents of EBX into the 4 bytes at memory address var. (Note, var is a 32-bit constant).
mov eax, [esi-4] ; Move 4 bytes at memory address ESI + (-4) into EAX
mov [esi+eax], cl ; Move the contents of CL into the byte at address ESI+EAX
mov edx, [esi+4*ebx] ; Move the 4 bytes of data at address ESI+4*EBX into EDX
mov eax, [ebx-ecx] ; Can only add register values
mov [eax+esi+edi], ebx ; At most 2 registers in address computation
Size Directives

However, in some cases the size of a referred-to memory region is ambiguous. Consider the instruction mov [ebx], 2 . Should this instruction move the value 2 into the single byte at address EBX ? Perhaps it should move the 32-bit integer representation of 2 into the 4-bytes starting at address EBX . Since either is a valid possible interpretation, the assembler must be explicitly directed as to which is correct. The size directives BYTE PTR , WORD PTR , and DWORD PTR serve this purpose, indicating sizes of 1, 2, and 4 bytes respectively.

mov BYTE PTR [ebx], 2 ; Move 2 into the single byte at the address stored in EBX.
mov WORD PTR [ebx], 2 ; Move the 16-bit integer representation of 2 into the 2 bytes starting at the address in EBX.
mov DWORD PTR [ebx], 2 ; Move the 32-bit integer representation of 2 into the 4 bytes starting at the address in EBX.

Instructions

We use the following notation:

<reg32> Any 32-bit register ( EAX , EBX , ECX , EDX , ESI , EDI , ESP , or EBP )
<reg16> Any 16-bit register ( AX , BX , CX , or DX )
<reg8> Any 8-bit register ( AH , BH , CH , DH , AL , BL , CL , or DL )
<reg> Any register
<mem> A memory address (e.g., [eax] , [var + 4] , or dword ptr [eax+ebx] )
<con32> Any 32-bit constant
<con16> Any 16-bit constant
<con8> Any 8-bit constant
<con> Any 8-, 16-, or 32-bit constant

Data Movement Instructions

The mov instruction copies the data item referred to by its second operand (i.e. register contents, memory contents, or a constant value) into the location referred to by its first operand (i.e. a register or memory). While register-to-register moves are possible, direct memory-to-memory moves are not. In cases where memory transfers are desired, the source memory contents must first be loaded into a register, then can be stored to the destination memory address.

Syntax
mov <reg>,<reg>
mov <reg>,<mem>
mov <mem>,<reg>
mov <reg>,<const>
mov <mem>,<const>

Examples
mov eax, ebx — copy the value in ebx into eax
mov byte ptr [var], 5 — store the value 5 into the byte at location var

push — Push stack (Opcodes: FF, 89, 8A, 8B, 8C, 8E, . )

The push instruction places its operand onto the top of the hardware supported stack in memory. Specifically, push first decrements ESP by 4, then places its operand into the contents of the 32-bit location at address [ESP]. ESP (the stack pointer) is decremented by push since the x86 stack grows down — i.e. the stack grows from high addresses to lower addresses. Syntax
push <reg32>
push <mem>
push <con32>

Examples
push eax — push eax on the stack
push [var] — push the 4 bytes at address var onto the stack

The pop instruction removes the 4-byte data element from the top of the hardware-supported stack into the specified operand (i.e. register or memory location). It first moves the 4 bytes located at memory location [SP] into the specified register or memory location, and then increments SP by 4.

Syntax
pop <reg32>
pop <mem> Examples
pop edi — pop the top element of the stack into EDI.
pop [ebx] — pop the top element of the stack into memory at the four bytes starting at location EBX.

The lea instruction places the address specified by its second operand into the register specified by its first operand. Note, the contents of the memory location are not loaded, only the effective address is computed and placed into the register. This is useful for obtaining a pointer into a memory region.

Syntax
lea <reg32>,<mem> lea edi, [ebx+4*esi] — the quantity EBX+4*ESI is placed in EDI.
lea eax, [var] — the value in var is placed in EAX.
lea eax, [val] — the value val is placed in EAX.

Arithmetic and Logic Instructions

The inc instruction increments the contents of its operand by one. The dec instruction decrements the contents of its operand by one.

Syntax
inc <reg>
inc <mem>
dec <reg>
dec <mem>

Examples
dec eax — subtract one from the contents of EAX.
inc DWORD PTR [var] — add one to the 32-bit integer stored at location var

The imul instruction has two basic formats: two-operand (first two syntax listings above) and three-operand (last two syntax listings above). The two-operand form multiplies its two operands together and stores the result in the first operand. The result (i.e. first) operand must be a register. The three operand form multiplies its second and third operands together and stores the result in its first operand. Again, the result operand must be a register. Furthermore, the third operand is restricted to being a constant value. Syntax
imul <reg32>,<reg32>
imul <reg32>,<mem>
imul <reg32>,<reg32>,<con>
imul <reg32>,<mem>,<con>

Examples

The idiv instruction divides the contents of the 64 bit integer EDX:EAX (constructed by viewing EDX as the most significant four bytes and EAX as the least significant four bytes) by the specified operand value. The quotient result of the division is stored into EAX, while the remainder is placed in EDX.

Syntax
idiv <reg32>
idiv <mem>

Examples

These instructions perform the specified logical operation (logical bitwise and, or, and exclusive or, respectively) on their operands, placing the result in the first operand location.

Syntax
and <reg>,<reg>
and <reg>,<mem>
and <mem>,<reg>
and <reg>,<con>
and <mem>,<con>

or <reg>,<reg>
or <reg>,<mem>
or <mem>,<reg>
or <reg>,<con>
or <mem>,<con>

xor <reg>,<reg>
xor <reg>,<mem>
xor <mem>,<reg>
xor <reg>,<con>
xor <mem>,<con>

Examples
and eax, 0fH — clear all but the last 4 bits of EAX.
xor edx, edx — set the contents of EDX to zero.

Logically negates the operand contents (that is, flips all bit values in the operand).

Syntax
not <reg>
not <mem>

Example
not BYTE PTR [var] — negate all bits in the byte at the memory location var.

neg — Negate

Performs the two’s complement negation of the operand contents.

Syntax
neg <reg>
neg <mem>

Example
neg eax — EAX → — EAX

These instructions shift the bits in their first operand’s contents left and right, padding the resulting empty bit positions with zeros. The shifted operand can be shifted up to 31 places. The number of bits to shift is specified by the second operand, which can be either an 8-bit constant or the register CL. In either case, shifts counts of greater then 31 are performed modulo 32.

Syntax
shl <reg>,<con8>
shl <mem>,<con8>
shl <reg>,<cl>
shl <mem>,<cl>

shr <reg>,<con8>
shr <mem>,<con8>
shr <reg>,<cl>
shr <mem>,<cl>

Examples

Control Flow Instructions

We use the notation <label> to refer to labeled locations in the program text. Labels can be inserted anywhere in x86 assembly code text by entering a label name followed by a colon. For example,

Transfers program control flow to the instruction at the memory location indicated by the operand.

Syntax
jmp <label>

Example
jmp begin — Jump to the instruction labeled begin .

These instructions are conditional jumps that are based on the status of a set of condition codes that are stored in a special register called the machine status word. The contents of the machine status word include information about the last arithmetic operation performed. For example, one bit of this word indicates if the last result was zero. Another indicates if the last result was negative. Based on these condition codes, a number of conditional jumps can be performed. For example, the jz instruction performs a jump to the specified operand label if the result of the last arithmetic operation was zero. Otherwise, control proceeds to the next instruction in sequence.

A number of the conditional branches are given names that are intuitively based on the last operation performed being a special compare instruction, cmp (see below). For example, conditional branches such as jle and jne are based on first performing a cmp operation on the desired operands.

Syntax
je <label> (jump when equal)
jne <label> (jump when not equal)
jz <label> (jump when last result was zero)
jg <label> (jump when greater than)
jge <label> (jump when greater than or equal to)
jl <label> (jump when less than)
jle <label> (jump when less than or equal to)

Example
cmp eax, ebx
jle done

Compare the values of the two specified operands, setting the condition codes in the machine status word appropriately. This instruction is equivalent to the sub instruction, except the result of the subtraction is discarded instead of replacing the first operand.

Syntax
cmp <reg>,<reg>
cmp <reg>,<mem>
cmp <mem>,<reg>
cmp <reg>,<con>

Example
cmp DWORD PTR [var], 10
jeq loop

These instructions implement a subroutine call and return. The call instruction first pushes the current code location onto the hardware supported stack in memory (see the push instruction for details), and then performs an unconditional jump to the code location indicated by the label operand. Unlike the simple jump instructions, the call instruction saves the location to return to when the subroutine completes.

The ret instruction implements a subroutine return mechanism. This instruction first pops a code location off the hardware supported in-memory stack (see the pop instruction for details). It then performs an unconditional jump to the retrieved code location.

Syntax
call <label>
ret

Calling Convention

In practice, many calling conventions are possible. We will use the widely used C language calling convention. Following this convention will allow you to write assembly language subroutines that are safely callable from C (and C++) code, and will also enable you to call C library functions from your assembly language code.

The C calling convention is based heavily on the use of the hardware-supported stack. It is based on the push , pop , call , and ret instructions. Subroutine parameters are passed on the stack. Registers are saved on the stack, and local variables used by subroutines are placed in memory on the stack. The vast majority of high-level procedural languages implemented on most processors have used similar calling conventions.

The calling convention is broken into two sets of rules. The first set of rules is employed by the caller of the subroutine, and the second set of rules is observed by the writer of the subroutine (the callee). It should be emphasized that mistakes in the observance of these rules quickly result in fatal program errors since the stack will be left in an inconsistent state; thus meticulous care should be used when implementing the call convention in your own subroutines.

>
Stack during Subroutine Call
[Thanks to Maxence Faldor for providing a correct figure and to James Peterson for finding and fixing the bug in the original version of this figure!]

A good way to visualize the operation of the calling convention is to draw the contents of the nearby region of the stack during subroutine execution. The image above depicts the contents of the stack during the execution of a subroutine with three parameters and three local variables. The cells depicted in the stack are 32-bit wide memory locations, thus the memory addresses of the cells are 4 bytes apart. The first parameter resides at an offset of 8 bytes from the base pointer. Above the parameters on the stack (and below the base pointer), the call instruction placed the return address, thus leading to an extra 4 bytes of offset from the base pointer to the first parameter. When the ret instruction is used to return from the subroutine, it will jump to the return address stored on the stack.

Caller Rules
  1. Before calling a subroutine, the caller should save the contents of certain registers that are designated caller-saved. The caller-saved registers are EAX, ECX, EDX. Since the called subroutine is allowed to modify these registers, if the caller relies on their values after the subroutine returns, the caller must push the values in these registers onto the stack (so they can be restore after the subroutine returns.
  2. To pass parameters to the subroutine, push them onto the stack before the call. The parameters should be pushed in inverted order (i.e. last parameter first). Since the stack grows down, the first parameter will be stored at the lowest address (this inversion of parameters was historically used to allow functions to be passed a variable number of parameters).
  3. To call the subroutine, use the call instruction. This instruction places the return address on top of the parameters on the stack, and branches to the subroutine code. This invokes the subroutine, which should follow the callee rules below.
  1. Remove the parameters from stack. This restores the stack to its state before the call was performed.
  2. Restore the contents of caller-saved registers (EAX, ECX, EDX) by popping them off of the stack. The caller can assume that no other registers were modified by the subroutine.

The result produced by _myFunc is now available for use in the register EAX. The values of the caller-saved registers (ECX and EDX), may have been changed. If the caller uses them after the call, it would have needed to save them on the stack before the call and restore them after it.

Callee Rules
  1. Push the value of EBP onto the stack, and then copy the value of ESP into EBP using the following instructions: This initial action maintains the base pointer, EBP. The base pointer is used by convention as a point of reference for finding parameters and local variables on the stack. When a subroutine is executing, the base pointer holds a copy of the stack pointer value from when the subroutine started executing. Parameters and local variables will always be located at known, constant offsets away from the base pointer value. We push the old base pointer value at the beginning of the subroutine so that we can later restore the appropriate base pointer value for the caller when the subroutine returns. Remember, the caller is not expecting the subroutine to change the value of the base pointer. We then move the stack pointer into EBP to obtain our point of reference for accessing parameters and local variables.
  2. Next, allocate local variables by making space on the stack. Recall, the stack grows down, so to make space on the top of the stack, the stack pointer should be decremented. The amount by which the stack pointer is decremented depends on the number and size of local variables needed. For example, if 3 local integers (4 bytes each) were required, the stack pointer would need to be decremented by 12 to make space for these local variables (i.e., sub esp, 12 ). As with parameters, local variables will be located at known offsets from the base pointer.

Example
Here is an example function definition that follows the callee rules:

In the body of the subroutine we can see the use of the base pointer. Both parameters and local variables are located at constant offsets from the base pointer for the duration of the subroutines execution. In particular, we notice that since parameters were placed onto the stack before the subroutine was called, they are always located below the base pointer (i.e. at higher addresses) on the stack. The first parameter to the subroutine can always be found at memory location EBP + 8, the second at EBP + 12, the third at EBP + 16. Similarly, since local variables are allocated after the base pointer is set, they always reside above the base pointer (i.e. at lower addresses) on the stack. In particular, the first local variable is always located at EBP — 4, the second at EBP — 8, and so on. This conventional use of the base pointer allows us to quickly identify the use of local variables and parameters within a function body.

The function epilogue is basically a mirror image of the function prologue. The caller’s register values are recovered from the stack, the local variables are deallocated by resetting the stack pointer, the caller’s base pointer value is recovered, and the ret instruction is used to return to the appropriate code location in the caller.

Learning Notes (Reverse Assembly) Day1-Day5

Of course, in addition to conventional, but also 3, 4 -. Even the symbols in the decimal system are not necessarily in the regular 123 order.
We can define a decimal system as follows: 9 7 5 3 1 enters 1 every 5; then 79 = 6 (10), 57 = 11 (10), 39 = 15 (10);
If the above symbols are used for encryption, it will bring great trouble to the decryptor.

2. Binary and hexadecimal:
Any information in a computer is stored in binary form.
Because of the complexity of binary writing, most software displays the data in the computer in hexadecimal system.
Any hexadecimal symbol has four corresponding binary symbols.
And hexadecimal is the abbreviation of binary, one hexadecimal = four binary numbers:
Binary: 0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111
Hexadecimal: 0 1 2 3 4 5 7 8 9 A B C D E F

3. Exercise:
a. 2+3 = 1. Is that true? Explain the reasons:
Answer: Yes, if you define a quaternary number 423 15, when 5 enters 1, then 4 = 0 (10), 2 = 1 (10), 3 = 2 (10), 1 = 3 (10);
Then 2 + 3 = 1 = 1 > 1 + 2 = 3;

b. Express the following binary numbers in hexadecimal:
1100 1011 0101 0100 1110 1011 0101 0111 1011 0100 1010 1011
Answer: C B 5 4 E B 5 7 B 8 A B

c. Express the following hexadecimal numbers in binary numbers:
4 8 7 F D C 1 2 0 A C E 6 9 B 9 5 3 F E
Answer: 0100 1000 0111 1111 1101 1100 0001 0010 0000 1010 1100 1110 0110 1001 1011 1001 0101 0011 1111 1110

d. Binary from 0 to 100 (100, 10 per line)
Answer: 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010
1011, 1100, 1101, 1110, 1111, 10000, 10001,10010,10011,10100
10101,10110,10111,11000,11001,11010,11011,11100,11101,11110
11111,100000,100001,100010,100011,100100,100101,100110,100111,101000
101001,101010,101011,101100,101101,101110,101111,110000,110001,110010
110011,110100,110101,110110,110111,111000,111001,111010,111011,111100
111101,111110,111111,1000000,1000001,1000010,1000011,1000100,1000101,1000110
1000111,1001000,1001001,1001010,1001011,1001100,1001101,1001110,1001111,1010000
1010001,1010010,1010011,1010100,1010101,1010110,1010111,1011000,1011001,1011010
1011011,1011100,1011101,1011110,1011111,1100000,1100001,1100010,1100011,1100100

(b) Binary arithmetic:

1. Octal:
Addition table:
1+1=2 1+2=3 1+3=4 1+4=5 1+5=6 1+6=7 1+7=10
2+2=4 2+3=5 2+4=6 2+5=7 2+6=10 2+7=11
3+3=6 3+4=7 3+5=10 3+6=11 3+7=12
4+4=10 4+5=11 4+6=12 4+7=13
5+5=12 5+6=13 5+7=14
6+6=14 6+7=15
7+7=16
Multiplication table:
1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7
2*2=4 2*3=6 2*4=10 2*5=12 2*6=14 2*7=16
3*3=11 3*4=14 3*5=17 3*6=22 3*7=25
4*4=20 4*5=24 4*6=30 4*7=34
5*5=31 5*6=36 5*7=43
6*6=44 6*7=52
7*7=61
2. The rule of addition, subtraction, multiplication and division:



3. Binary:

4. Exercise:
1. 9-ary definition: consisting of 9 symbols, 2, 9, 1, 7, 6, 5, 4, 8, 3, every 9 into 1;
Computation: 123 + 234 = 725;
2. 10-digit definition: composed of 10 symbols, respectively: @ calculation: @$B +% AC &= &!@%;
3. Definition of 3-digit system: It consists of three symbols: 2, 0 and 1, each with 3 entering 1;
2 0 1
02 00 01
12 10 11
022
Computation: 12 + 02 = 022;
Definition of 4.7-digit system: composed of seven symbols: 8, 3, 4, 2, 9, 5, 6, every seven in 1;
8 3 4 2 9 5 6
38 33 34 32 39 35 36
48 43 44 42 49 45 46
28 23 24 22 29 25 26
98 93 94 92 99 95 96
58 53 54 52 59 55 56
388 383
Computation: 92 + 39 = 68;

Day 2 "Data Width & Logical Operations"

(1) Data width:

1. Data Width Meaning:

a. In computer, due to hardware conditions, the data are all of length, and data beyond the maximum width will be lost.

b. Important of these data widths are:
BYTE 8 bit 1 byte
WORD word 16bit 2 bytes
DWORD double-word 32bit 4 bytes

c. Data type diagrams:

2. Logical operations:

a. or operation (or |) is 1 if only one is 1; 0101 | 0001 = 0101;

b. and operation (and &) must be two to one, to be 1; 0101 & 0001 = 0001;

c. XOR (xor ^) is different from each other, only 1; 0101 ^ 0001 = 0100;

d. not!) 1 is 0, 0 is 1;! 0101 = 1010;! 0001 = 1110;

3. Additive calculation of cpu:

4. Simple encryption and decryption:

5. Homework in this section:

Octal number 2-5 in the computer results are: 1777777777777777777777777777775, why?
Answer:

Use XOR to encrypt 87AD6 and then decrypt it. Encryption key: 5;
Answer:

Calculate 2-3 = with logical operations only? (Concerning content: logical operation, shift, data width)
Answer:

6. Registers

Day 3 Register

(1) General Register

1. Meaning:
Universal registers are one kind of registers. Some registers are special, but those without specific purpose are general registers.
Assembler is: the more complex the data is moving, the more complex the program is between memory and registers, between registers and registers. If we know the flow of these data, we can know the process of the program. The reverse is to know how the data moves.
2.32-bit register classification:

(2) Register Operational Grammar

1.MOV target operands, source operands
Function: Copy source operands to target operands

1. Source operands can be immediate numbers, general registers, segment registers, memory units
2. Target operands can be general registers, segment registers, or memory units
3. Operator widths must be the same
4. Source and target operands cannot be both memory units

2.ADD target operands, source operands
Role: Add source operation values to target operands

1. The width of the target operand must be the same as that of the source operand, but not the same when the source operand is an immediate number.
2. Immediate number as source operand can be smaller than target number or larger than target number. Excess bits are lost and missing bits are added 0.

3.SUB target operands, source operands
Role: Target Operator minus Source Operator

1. The width of the target operand and the source operand must be the same, except for the immediate number.
2. Immediate number as source operand can be smaller than target number or larger than target number. The excess bits are lost and the missing bits are complemented by 0.

4.AND Target Operator, Source Operator
Function: Bit operation -, the target operand and the source operand -, and save the value to the target operand;

5.OR target operands, source operands
Role: Bit operation |, the target operand and source operand | operation, and save the value to the target operand;

6.XOR target operands, source operands
Function: Bit operation ^, the target operand and source operand are ^ operated, and the value is saved to the target operand;

7.NOT Target Operator
Function: Reverse the target operand

(3) Register knowledge

1. The Difference between Register and Memory

  1. The registers are located in the CPU, which have fast execution speed but high cost.
  2. Memory speed is relatively slow, but low cost, can be used in large quantities;
  3. There is no essential difference between registers and memory, they are all containers for storing data, and they are all fixed-width.
  4. At the grass-roots level, there are 8 common ones: EAX, ECX, EDX, EBX, ESP, EBP, ESI and EDI.
  5. Several commonly used measurement units in computer: BYTE, WORD, DWORD;

2. Memory (not disk/hard disk storage size here)

  1. The 32-bit computer refers to the addressing number, that is, the addressing width. Each number represents a byte. 0xFFFFFFFFFF + 1 = 100000000, which is the largest amount of computer storage, its decimal number: 4294967296 bytes, or 4G. (32-bit system can patch programming, 64-bit recognition of more memory);
  2. The amount of memory is too large to give each cell a name, so it is replaced by a number. The reason for 32-bit computers is that their registers are 32-bit, but they are not accurate. Many registers are larger than 32-bit.

3. Memory format:


1. Each memory unit has a unique number. The width of each memory unit is 8 bits, that is, 1 byte.
2. [Number] is called address;
3. The function of address: when we want to read data from memory or write data to memory, we need to use it.

4. Write/read data from specified memory:

dword: Represents how wide you want to read/write, which is 4 bytes.
ptr: This means that there is a pointer behind it (that is, there is an address in it, not an ordinary value).
ds: Data segment register;
0x012FF9AC: Memory number (must be 32 bits), the first 0 can be omitted;
Note: Address numbers should not be written casually, because memory is protected, not all memory can be read and written directly (need special processing), you can use the data in esp for memory use.

Day 4 Memory Address and Stack

(1) Data storage and addressing

(1) Data window and stack window

The image above is a data window. The stack window is not visible in vs.
The memory unit of 0x006FF890 corresponds to 20 blocks of memory. The order from left to right is 0x006FF891, 0x006FF892. 0x006FF89F;
The stack window can be viewed from the VTDeBug software, and its corresponding memory units are exactly the opposite, and should be viewed from right to left.

(2) Addressing Formula

The difference between lea and mov is that lea passes the address of the source operand to the target operand and mov passes the value of the source operand to the target operand.

(3) Simulated stack

(4) PUSH and POP instructions

Both PUSH and POP instructions change the value of esp, that is, the system treats esp as the top of the stack.
Both PUSH and POP instructions cannot use 8-bit registers.
Not every use is reduced by 4, but by the type of data pressed and popped up.

(5) PUSHAD instruction pre-POPAD instruction

The pushad and popad instructions here change the value of esp on the top of the stack.

Day 5 Logo Register

(1) 32-bit flag register EFLAGS

  1. Flag registers are used to store information about the state of the CPU and the characteristics of the operation results after an instruction has been executed.
  2. Logo registers are sequential and must be remembered.
(2) Marker bit of operation result

Carry Flag: No. 0
If the highest bit of the result of the operation produces a carry or borrow, the value is 1, otherwise it is 0.

Parity Flag: No. 2
It is used to reflect the parity of the number of "1" in the result of operation. If the number of "1" is even, PF=1, otherwise 0;

3. Auxiliary Carry Flag: No. 4
In the following cases, the value of the auxiliary bit identifier AF is set to 1, otherwise the value is 0:
(1) When the word operation occurs, the low-bit bytes carry or borrow from the high-bit bytes;
(2) When the byte operation occurs, the lower 4 bits move to the higher 4 bits carry or borrow;

4. Zero Flag: No. 6
If the result of operation is 0, the value is 1, otherwise it is 0. This flag bit can be used to judge whether the result of operation is 0 or not.

5. Sign Flag: No. 7
The symbolic bits reflecting the results of operations are the same as the highest bits of the results of operations.

6. Overflow Flag: No. 11
Reflecting whether the result of sign number addition and subtraction is overflow or not, if the result exceeds the scope of the current operation digit, it is called overflow, OF=1, otherwise OF=0.

Here we should pay attention to the difference between carry mark and carry mark.
The difference between top carry and overflow:
1. Carry mark indicates whether the result of unsigned arithmetic exceeds the range, and overflow mark indicates whether the result of signed arithmetic exceeds the range.
2. Positive + positive = positive, if the result is negative, then there is an overflow; negative + negative = negative, if the result is positive, there is an overflow;
3. Positive + negative will never spill over.

CF bit is what we need to pay attention to when we do unsigned operation. In symbolic operations, we should pay attention to the OF bit.

(3) Relevant Directives

ADC instruction: carry-in addition
Format: ADC R/M, R/M/IMM. Both sides should not be in memory at the same time. Width should be the same.

The only difference from ADD is that when ADC performs an addition operation, the CF bit values are added together to the target operand.
So ADC is often used to deal with the following situations:
If you have to deal with very large integers that cannot be stored in the length of double-word data (the maximum length that ADD can use), you can divide the values into multiple double-word data elements and perform an independent addition operation on each element.
In order to complete this operation correctly, carry flags for each addition operation must be detected. If carry flags are set to 1, they must be carried to the next pair of added data elements.

2.SBB Directive: Subtraction with Borrows
Format: SBB R/M, R/M/IMM can not be both sides of memory, the width should be the same.

Operating Object 1 = Operating Object 1 — Operating Object 2-CF where CF is carry value

3.XCHG Directive: Exchange Data
Format: XCHG R/M, R/M can not be both sides of memory, the width should be the same.

4.MOVS Directive: Mobile Data Memory-Memory
Note: MOVS R/M, R/M, both sides of this instruction can be memory
The MOVS instruction is used to copy a data item (byte, word or double word) from the source string to the target string. The source string indicates that DS: SI and ES: DI point to the target string.

Moves instruction (string transfer) (string operation instruction) (MOVSB transfer character. MOVSW transfer word. MOVSD transfer double word.)
Here MOVS copies four bytes of content, namely DWORD, and another form of writing is MOVSD.
Correspondingly, two bytes of MOVSW instructions and one byte of MOVSB instructions are copied.
Note the direction of the ESI and EDI copies. The direction of the copies depends on the direction flag D.

5.STOS Directive: Store the value of AL/AX/EAX into the memory unit specified in [EDI]

We can use the REP prefix, as well as two bytes of STOSW instructions per operation and one byte of STOS instructions per operation.

6.REF Directive: Repeat the string command as many times as specified in the count register (ECX)
__asm <
mov eax, 0x12345678

Читать:
Woff как установить на компьютер

Похожие статьи