Skip to content

Latest commit

 

History

History
 
 

x86-64

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Ассемблер архитектуры x86/x86-64

Основной reference по набору команд преобразованный в HTML.

Reference по наборам команд MMX, SSE и AVX на сайте Intel.

Неплохой учебник по ассемблеру x86 на WikiBooks

Синтаксис AT&T и Intel

Исторически сложилось два синтаксиса языка ассемблера x86: синтаксис AT&T, используемый в UNIX-системах, и синтаксис Intel, используемый в DOS/Windows.

Различие, в первую очередь, относится к порядку аргументов команд.

Компилятор gcc по умолчанию использует синтаксис AT&T, но с указанием опции -masm=intel может переключаться в синтаксис Intel.

Кроме того, можно указать используемый синтаксис первой строкой в тексте самой программы:

.intel_syntax noprefix

Здесь параметр noprefix после .intel_syntax указывает на то, что помимо порядка аргументов, соответствующих синтаксису Intel, ещё и имена регистров не должны начинаться с символа %, а константы - с символа $, как это принято в синтаксисе AT&T.

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

Регистры процессора общего назначения

Исторически семество процессоров x86 унаследовало набор 8-битных регистров общего назначения семества 8080/8085, которые назывались a, b, c и d. Но поскольку процессор 8086 стал 16-битным, то регистры стали назваться ax, bx, cx и dx. В 32-битных процессорах они называются eax, ebx, ecx и edx, в 64-битных rax, rbx, rcx и rdx.

Кроме того, в x86 есть регистры "двойного назначения", которые можно использовать, в том числе, в качестве регистров общего назначения, если пользоваться ограниченным подмножеством команд процессора:

  • rbp - верхняя граница стека;
  • rsi - индекс элемента массива, из которого выполняется копирование;
  • rdi - индекс элемента массива, в который выполняется копирование.

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

Регистры x86-64

64-разрядные регистры для архитектуры x86-64 именуются начиная с буквы r. Помимо регистров rax...rsi, rdi можно использовать регистры общего назначение r9...r15. Указатель стека хранится в rsp, верхняя граница стекового фрейма - в rbp.

Младшие 32-разрядные части регистров rax...rsi,rdi,rsp,rbp можно адресовать по именам eax...esi,edi,esp,ebp. При записи значений по 32-битным именам регистров, старшие 32 разряда обнуляются, что приемлемо для операций над 32-разрядными беззнаковыми значениями.

Для работы со знаковыми 32-разрядными значениями, например типом int, необходимо предварительно выполнять операции знакового расширения с помощью команды movslq

Некоторые инструкции

Для синтаксиса Intel первым аргументов команды является тот, значение которого будет модифицировано, а вторым - которое остается неизменным.

add     DST, SRC        /* DST += SRC */
sub     DST, SRC        /* DST -= SRC */
inc     DST             /* ++DST */
dec     DST             /* --DST */
neg     DST             /* DST = -DST */
mov     DST, SRC        /* DST = SRC */
imul    SRC             /* (eax,edx) = eax * SRC - знаковое */
mul     SRC             /* (eax,edx) = eax * SRC - беззнаковое */
and     DST, SRC        /* DST &= SRC */
or      DST, SRC        /* DST |= SRC */
xor     DST, SRC        /* DST ^= SRC */
not     DST             /* DST = ~DST */
cmp     DST, SRC        /* DST - SRC, результат не сохраняется, */
test    DST, SRC        /* DST & SRC, результат не сохраняется  */
adc     DST, SRC        /* DST += SRC + CF */
sbb     DST, SRC        /* DST -= SRC - CF */

Для синтаксиса AT&T порядок аргументов - противоположный, то есть команда add %rax, %rbx вычислит сумму %rax и %rbx, после чего сохранит результат в регистр %rbx, который указан вторым аргументом.

Флаги процессора

В отличии от процессоров ARM, где обновление регистра флагов производится только при наличии специального флага в команде, обозначаемого суффиксом s, в процессорах Intel флаги обновляются всегда большинстом инструкций.

Флаг ZF устанавливается, если в результате операции был получен нуль.

Флаг SF устанавливается, если в результате операции было получено отрицательное число.

Флаг CF устанавливается, если в результате выполнения операции произошел перенос из старшего бита результата. Например, для сложения CF устанавливается если результат сложения двух беззнаковых чисел не может быть представлен 64-битным беззнаковым числом.

Флаг OF устанавливается, если в результате выполняния операции произошло переполнение знакового результата. Например, при сложении OF устанавливается, если результат сложения двух знаковых чисел не может быть представлен 64-битным знаковым числом.

Обратите внимание, что и сложение add, и вычитание sub устанавливают одновременно и флаг CF, и флаг OF. Сложение и вычитание знаковых и беззнаковых чисел выполняется совершенно одинаково, и поэтому используется одна инструкция и для знаковой, и для беззнаковой операции.

Инструкции test и cmp не сохраняют результат, а только меняют флаги.

Управление ходом программы

Безусловный переход выполняется с помощью инструкции jmp

jmp label

Условные переходы проверяют комбинации арифметических флагов:

jz      label   /* переход, если равно (нуль), ZF == 1 */
jnz     label   /* переход, если не равно (не нуль), ZF == 0 */
jc      label   /* переход, если CF == 1 */
jnc     label   /* переход, если CF == 0 */
jo      label   /* переход, если OF == 1 */
jno     label   /* переход, если OF == 0 */
jg      label   /* переход, если больше для знаковых чисел */
jge     label   /* переход, если >= для знаковых чисел */
jl      label   /* переход, если < для знаковых чисел */
jle     label   /* переход, если <= для знаковых чисел */
ja      label   /* переход, если > для беззнаковых чисел */
jae     label   /* переход, если >= (беззнаковый) */
jb      label   /* переход, если < (беззнаковый) */
jbe     label   /* переход, если <= (беззнаковый) */

Вызов функции и возврат из неё осуществляются командами call и ret

call    label   /* складывает в стек адрес возврата, и переход на label */
ret             /* вытаскивает из стека адрес возврата и переходит к нему */

Кроме того, есть составная команда для организации циклов, которая подразумевает, что в регистре ecx находится счётчик цикла:

loop    label   /* уменьшает значение ecx на 1; если ecx==0, то
                   переход на следующую инструкцию, в противном случае
                   переход на label */

Адресация памяти

В отличии от RISC-процессоров, x86 позволяет использовать в качестве один из аргументов команды как адрес в памяти.

В синтаксисе AT&T такая адресация записывается в виде: OFFSET(BASE, INDEX, SCALE), где OFFSET - это константа, BASE и INDEX - регистры, а SCALE - одно из значений: 1, 2, 4 или 8.

Адрес в памяти вычисляется как OFFSET+BASE+INDEX*SCALE. Параметры OFFSET, INDEX и SCALE являются опциональными. При их отсутсвтвии подразумевается, что OFFSET=0, INDEX=0, SCALE равен размеру машинного слова.

В синтаксисе Intel используется более очевидная нотация: [BASE + INDEX * SCALE + OFFSET].

Соглашения о вызовах для 64-разрядной архитектуры SystemV AMD64 ABI

Целочисленные аргументы передаются последовательно в регистрах: rdi, rsi, rdx, rcx, r8, r9. Если передается более 6 аргументов, то оставшиеся - через стек.

Вещественные аргументы передаются через регистры xmm0...xmm7.

Возвращаемое значение целочисленного типа должно быть сохранено в rax, вещественного - в xmm0.

Вызываемая функция обязана сохранять на стеке значения регистров общего назначения rbx, rbp, и регистры r12...r15.

Кроме того, при вызове функции для 64-разрядной архитектуры есть дополнительное требование - перед вызовом функции стек должен быть выровнен по границе 16 байт, то есть необходимо уменьшить значение rsp таким образом, оно было кратно 16. Если кроме регистров задействуется стек для передачи параметров, то они должны быть прижаты к нижней выровненной границе стека.

Для функций гарантируется 128-байтная "красная зона" в стеке ниже регистра rsp - область, которая не будет затронута внешним событием, например, обработчиком сигнала. Таким образом, можно задействовать для адресации локальных переменных память до rsp-128.