Skip to content

Latest commit

 

History

History
 
 

Assembly

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Ассемблер x86 AT&T

Компиляция

Для начала на 64-битной Ubuntu выполним sudo apt-get install gcc-multilib

gcc prog.S -o prog

gcc -m32 prog.S simpleio_i686.S -o prog

gcc -m32 -nostdlib prog.S -o prog — без стандартной библиотеки языка Си и startup кода

Тогда точка входа в программу называется _start, а для завершения программы нужно использовать системный вызов exit

Название файла:

.S если нужен препроцессор Cи

.s если не нужен препроцессор Cи

Общее

  • Комментарии как в языке Cи
  • Символьные константы как в Cи
  • Строки как в Cи, но без неявного \0
  • Каждая инструкция процессора на отдельной строке
  • Инструкции можно помечать LABEL:
  • Метки это не переменные, а символические константы, значение которых известно при компиляции
  • Директива ассемблера управляет трансляцией
  • Инструкция транслируется в машинный код

Целое число (32 бит) может быть:

  • Знаковым целым числом
  • Беззнаковым целым числом
  • Указателем любого типа

Тип никак не привязан к ячейке/регистру, в котором хранится число. Интерпретация числа зависит от выполняющейся инструкции.

У целочисленных констант поддерживаются различные основания:

  • десятичное (суффикс d или префикс 0d)
  • двоичное (суффикс b или префикс 0b)
  • восьмеричное (суффикс q или префикс 0o)
  • шестнадцатеричное (суффикс h или префикс 0h либо 0x)

Структура единицы трансляции

Секции — логические части программы

  • .text — код программы и readonly data
  • .data — глобальные переменные (данные в этой секции можно модифицировать)
  • .bss — инициализированные нулём глобальные переменные

Компоновщик объединяет кусочки секций из разных единиц трансляции в одну большую секцию.

Константы и константные строки могут размещаться в .text или в .rodata

Глобальные переменные размещаются в .data или в .bss

Дополнительные секции можно обозначать так:

.section .rodata, "a"

Метка — адрес, по которому размещается инструкция при выполнении программы.

Чтобы сделать метку NAME доступной компоновщику пишем .global NAME

Точка входа в программу — метка, на которую передаётся управление в начале выполнения программы.

Точки входа:

.global _start если без стандартной библиотеки Си

.global main со стандратной библиотекой Си

Простейшая программа на ассемблере будет выглядеть так:

    .text           // секция кода программы
    .global main    // экспортируем точку входа
main:
    call finish     // вызываем подпрограмму finish // exit(0)

Определение данных

.align n задаёт выравнивание данных в секции

extern говорит компилятору, что переменная определена в другой единице трансляции

const в Си используется как обозначение того, что память, в которой размещена переменная, является readonly

Глобальные переменные:

.byte 1, 2, 3, '\n'     // размер: 8 бит
.short 10, 11           // размер: 16 бит
.int 0xff00ff00, 20     // размер: 32 бит
.quad -1                // размер: 64 бит
.float 1.5
.double 2.0
.ascii "abc"
.asciz "Hello" // строка завершается байтом \0

Резервирование памяти под массив:

.skip 4 * 1024, 0
.space  64 * 4, 0 // выделяет пространство размера 64 * 4, заполненное байтом 0

Как правило, определение данных должно быть помечено. Например:

str1:
    .asciz  "Hello, there\n"

Затем метка str1 может использоваться в программе:

    movl    $str1, %esi // положили в %esi адрес строки

Регистры

 31            0
+-------+---+---+
|       |AH |AL |  EAX  // младшие 16 бит : AX
|       |BH |BL |  EBX  // младшие 16 бит : BX
|       |CH |CL |  ECX  // младшие 16 бит : CX
|       |DH |DL |  EDX  // младшие 16 бит : DX
|       |  SI   |  ESI
|       |  DI   |  EDI
|       |  BP   |  EBP  // регистр текущего фрейма в стеке
|       |  SP   |  ESP  // указатель стека

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

%cl — счётчик сдвига

eip — адрес следeдующей инструкции

eflags — регистр флагов процессора

Регистры %ebx, %esi, %edi можем использовать для хранения промежуточных результатов.

При манипуляциях с младшим байтом (%al, %bl, %cl, %dl) остальные байты регистра не изменяются.

Адресация

mov x, %edi   // загружаем данные по адресу x
mov $x, %edi  // кладём адрес x в регистр

(%esp) - скобки означают обращение к памяти по адресу, который лежит в регистре

Методы адресации:

  • Регистровый — указывается имя регистра

    movl %esp, %ebp
  • Непосредственный — аргумент задается в инструкции

    movb $16, %cl
  • Прямой — адрес ячейки памяти задается в инструкции

    movl %eax, var1

Аргумент инструкции, обращающийся к памяти, имеет следующий общий вид:

OFFSET(BASE, INDEX, SCALE)

OFFSET — это 32-битное значение, которое можно рассматривать либо как смещение, либо как базовый адрес в памяти.

BASE — это регистр процессора.

INDEX — это регистр процессора.

SCALE — это число из {1, 2, 4, 8}.

Адрес для обращения к памяти вычисляется по формуле:

 OFFSET + BASE + INDEX * SCALE

Примеры:

movl (%eax), %eax  // в регистр %eax записать 4 байта, расположенные по адресу, который хранился изначально в %eax

movl %ecx, -8(%ebx) // сохранить значение из регистра %ecx по адресу %ebx - 8

movl arr(,%esi,4), %eax // в регистр %eax записать значение, расположенное по адресу arr + %esi * 4

movl -64(%ebp,%esi,4), %eax // в регистр %eax записать значение, расположенное по адресу %ebp - 64 + %esi * 4

Флаги

  • ZF (бит 6) — флаг нулевого результата
  • SF (бит 7) — флаг отрицательного результата
  • CF (бит 0) — флаг переноса из старшего бита
  • OF (бит 11) — флаг переполнения

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

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

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

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

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

Инструкции ADD, SUB, CMP, INC устанавливают флаги в зависимости от результата

  • IMUL устанавливает OC в зависимости от представимости результата 32 битами, Z – неопределен, S – старший бит младших 32 битов

  • LEA, MOV – не изменяет флаги

  • AND, TEST, OR, XOR – обнуляют O, C, устанавливают S и Z в зависимости от результата

readi32 / readi64 при успешном чтении сбрасывает флаг CF, иначе устанавливает

Переходы

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
js  label   // переход, если SF == 1
jns label   // переход, если SF == 0
jg  label   // переход, если больше для знаковых чисел
jge label   // переход, если >= для знаковых чисел
jl  label   // переход, если < для знаковых чисел
jle label   // переход, если <= для знаковых чисел
ja  label   // переход, если > для беззнаковых чисел
jae label   // переход, если >= (беззнаковый)
jb  label   // переход, если < (беззнаковый)
jbe label   // переход, если <= (беззнаковый)

Стек

Стек растёт вниз по адресам

Куча растёт вверх по адресам

На стек можно сохранять только 32-битные значения

%esp - указывает на самый младший по адресам элемент стека

push %eax уменьшает на 4 значение %esp, потом кладёт значение %eax по адресу %esp

pop %eax кладёт значение по адресу %esp в %eax, потом увеличивает на 4 значение %esp

ret — возврат из подпрограммы, для этого в верхушке стека (по адресу (%esp)) должен находиться адрес возврата

call — кладёт в стек %eip как адрес возврата, затем помещает в %eip адрес вызываемой подпрограммы

Выделение памяти

sub $4, %esp   // выделили 4 байта под локальную переменную
mov $42, (%esp)

Сохранение регистров

push %ebx
push %esi
/* код подпрограммы */
pop %esi
pop %ebx
ret

Организация стекового кадра

Регистр %ebp хранит адрес стекового кадра текущей подпрограммы

(%ebp) — адрес стекового кадра предыдущей подпрограммы, ((%ebp) — пред-предыдущей…

Самый внешний стековый кадр хранит 0.

// Стандартный пролог :
pushl %ebp
movl %esp, %ebp

/* код подпрограммы */

// Стандартный эпилог:
movl %ebp, %esp
popl %ebp
ret

Выравнивание

Linux x86 не требует, но рекомендует, а MacOS требует выравнивания стека по 16 байтам.

При вызове подпрограммы первый аргумент должен находиться по адресу, кратному 16.

Если выравнивание стека неизвестно:

and $-16, %esp // смещаем ESP вниз на правильную границу

Подпрограммы

При вызове подпрограммы первый аргумент должен находиться по адресу, кратному 16.

extern "C" чтобы вызвать в C++ неманглированную ассемблерную функцию

call LABEL — вызов подпрограммы

ret — возврат из подпрограммы

Calling convention:

  • %eax или %edx:%eax для возврата значения из подпрограммы
  • %eax, %ecx, %edxscratch
  • %ebx, %esi, %edi, %ebpcallee-saved
  • Параметры передаются через стек
  • Параметры заносятся в обратном порядке
  • Стек очищается тем, кто вызвал подпрограмму (caller-cleaned)

Если подпрограмма использует регистры callee-saved, они должны быть сохранены в начале и восстановлены перед возвратом из нее.

Передача аргументов в подпрограмму

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

Если размер аргумента меньше чем 32 бита:

pushl $'\n'   // всё равно выделяются 4 байта, байт \n - младший
call putchar

Если размер аргумента 64 бита:

// сохраняем в стек число 1LL как LE-значения (младший байт лежит по младшему адресу)
pushl $0
pushl $1

Доступ к аргументам

Используются положительные смещения относительно %ebp:

movl 8(%ebp), %eax   // доступ к 1-му параметру

Ниже %ebp хранятся сохраненные регистры и область под локальные переменные

Ввод-вывод с simpleio

Чтение целого числа со стандартного потока ввода:

call readi32

Поcле исполнения подпрограммыв регистре %eax находится считанное число.

Если произошла ошибка преобразования или был достигнут конец файла, флаг CF устанавливается, а при успешном чтении сбрасывается.

Вывод целого числа на стандартный поток вывода:

// тут в регистр %eax должно быть помещено выводимое число
call writei32

Вывод символа \n:

call nl

Чтение 64-битного целого числа со стандартного потока ввода:

call    readi64

Поcле исполнения подпрограммы в регистрах %eax (младшие 32 бита) и %edx (старшие 32 бита) находится считанное число. Если произошла ошибка преобразования или был достигнут конец файла, флаг CF устанавливается, а при успешном чтении сбрасывается.

Вывод 64-битного целого числа на стандартный поток вывода:

// тут в регистры %eax (младшие 32 бита), %edx (старшие 32 бита) должно быть помещено выводимое число
call writei64

Инструкции

Суффиксы: b — 8 бит w — 16 бит l — 32 бит q — 64 бит

Все инструкции

addl    SRC, DST   // DST += SRC
subl    SRC, DST   // DST -= SRC
incl    DST        // ++DST
decl    DST        // --DST
negl    DST        // DST = -DST
movl    SRC, DST   // DST = SRC
imull   SRC        // (%eax,%edx) = %eax * SRC - знаковое
mull    SRC        // (%eax,%edx) = %eax * SRC - беззнаковое
andl    SRC, DST   // DST &= SRC
orl     SRC, DST   // DST |= SRC
xorl    SRC, DST   // DST ^= SRC
notl    DST        // DST = ~DST
cmpl    SRC, DST   // DST - SRC, результат не сохраняется
testl   SRC, DST   // DST & SRC, результат не сохраняется
adcl    SRC, DST   // DST += SRC + CF
sbbl    SRC, DST   // DST -= SRC - C

mov

mov откуда, куда // копирование

Типы пересылок:

  • Регистр-регистр
  • Регистр-память
  • Память-регистр

xor

xorl %esi, %esi   // %esi = 0

mul / imul

Команда Результат
mulb %al 16 бит: %ax
mulw %ax 32 бита: младшая часть в %ax, старшая в %dx
mull %eax 64 бита: младшая часть в %eax, старшая в %edx

set

Заносит в аргумент значение 0 или 1 в зависимости от установленных флагов.

Аргументом может быть: %al, %bl, %cl, %dl или байт в памяти.

Инструкция Устанавливает 1, если
SETA above (CF=0 and ZF=0).
SETAE above or equal (CF=0).
SETB below (CF=1).
SETBE below or equal (CF=1 or ZF=1).
SETC carry (CF=1).
SETE equal (ZF=1).
SETG greater (ZF=0 and SF=OF).
SETGE greater or equal (SF=OF).
SETL less (SF != OF).
SETLE less or equal (ZF=1 or SF != OF).
SETNA not above (CF=1 or ZF=1).
SETNB not below (CF=0).
SETNC not carry (CF=0).
SETNE not equal (ZF=0).
SETNG not greater (ZF=1 or SF != OF).
SETNL not less (SF=OF).
SETNO not overflow (OF=0).
SETNP not parity (PF=0).
SETNS not sign (SF=0).
SETNZ not zero (ZF=0).
SETO overflow (OF=1)
SETP parity (PF=1).
SETPE parity even (PF=1).
SETPO parity odd (PF=0).
SETS sign (SF=1).
SETZ zero (ZF=1).

lea

lea источник, куда

Адрес источника копируется, без обращения по этому адресу

Источник должен находиться в памяти (не может быть непосредственным значением — константой или регистром)

test

    testb $0b00001000, %al  /* установлен ли 3-й (с нуля) бит?   */
    je    not_set
    /* нужные биты установлены */
not_set:
    /* биты не установлены */
    testl %eax, %eax
    je    is_zero
    /* %eax != 0 */
is_zero:
    /* %eax == 0 */

xchg

Обменивает значение из регистра/памяти со значением регистра.

xchg %esi, %edi

lahf

Не принимает аргументов!

Загружает: %ah ← EFLAGS(SF:ZF:0:AF:0:PF:1:CF)

Сдвиги

команда количество, назначение

Количество может быть задано в коде или находиться в регистре %cl(младшие 5 бит)

Арифметические

При сдвиге вправо старший бит заполняется знаковым битом

sal %eax        // %eax <<= 1
sar %eax        // %eax >>= 1
sal $2, %eax    // %eax <<= 2
sal %cl, %eax   // %eax <<= %cl & 0x1F
sarl $4, %eax
       31                                    0
      +---------------------------------------+
до    |1000 0000 0000 0000 1111 0000 0000 0000|
      +---------------------------------------+
после |1111 1000 0000 0000 0000 1111 0000 0000|
      +---------------------------------------+
sarl $4, %eax
       31                                    0
      +---------------------------------------+
до    |0000 0000 0000 0000 1111 0000 0000 0000|
      +---------------------------------------+
после |0000 0000 0000 0000 0000 1111 0000 0000|
      +---------------------------------------+

Логические

При сдвиге вправо старший бит заполняется нулём

shl CNT, DST   // влево
shr CNT, DST   // вправо
shrl $4, %eax
       31                                    0
      +---------------------------------------+
до    |1000 0000 0000 0000 1111 0000 0000 0000|
      +---------------------------------------+
после |0000 1000 0000 0000 0000 1111 0000 0000|
      +---------------------------------------+
shrl $4, %eax
       31                                    0
      +---------------------------------------+
до    |0000 0000 0000 0000 1111 0000 0000 0000|
      +---------------------------------------+
после |0000 0000 0000 0000 0000 1111 0000 0000|
      +---------------------------------------+

Вращения

rol CNT, DST   // влево
ror CNT, DST   // вправо
roll $4, %eax
       31                                    0
      +---------------------------------------+
до    |1000 0000 0000 0000 1111 0000 0000 0101|
      +---------------------------------------+
после |0000 0000 0000 1111 0000 0000 0101 1000|
      +---------------------------------------+

Вращения через CF

rcl CNT, DST   // влево
rcr CNT, DST   // вправо
rcrl %eax
       CF  31                                    0
      +---+---------------------------------------+
до    | C |1001 0000 0000 0000 1111 0000 0000 0001|
      +---+---------------------------------------+
после | 1 |C100 1000 0000 0000 0000 0111 1000 0000|
      +---+---------------------------------------+

Расширения

Расширение нулями:

movzbl %al, %esi // 8 -> 32 бита
movzwl %ax, %esi // 16 -> 32 бита

Расширение знаковым битом:

movsbl %al, %esi // 8 -> 32 бита
movswl %ax, %esi // 16 -> 32 бита

cdq

Не принимает аргументов!

Знаковое расширение %eax%edx:%eax

Работа с 64-битными целыми

64-битные целые требуют по несколько инструкций для обработки

64-битное значение хранится в паре регистров (напр. %eax и %edx)

  • Логические операции — отдельно для младшей и старшей половины
  • Cложение — ADD для младшей половины, ADC для старшей
  • Вычитание — SUB для младшей половины, SBB для старшей
  • Умножение, деление — вспомогательные функции (находятся в libgcc или аналогичной библиотеке)

Сдвиги 64-битных чисел

Пусть младшая часть числа лежит в %eax, старшая в %edx

Можно сдвигать по одному биту и использовать CF

тогда сдвиг влево на 1:

shll %eax
rcll %edx

Или через специальные инструкции shld/shrd для старшей части

тогда сдвиг влево на 7:

shld $7, %eax, %edx
shll $7, %eax

тогда сдвиг вправо на 7:

shrd $7, %edx, %eax
shrl $7, %edx

Полезные ссылки

wikibook

архитектура ЭВМ и язык ассемблера

инструкции и флаги

в столбце tested_f указаны флаги процессора, которые влияют на исполнение инструкции, modif_f - это флаги, значение которых может измениться в результате выполнения инструкции, def_f - это только те флаги, значение которых может измениться специфицированным образом, undef_f - это флаги, значение которых после выполнения инструкции не определено.

документация gcc

справочник инструкций