Skip to content

Latest commit

 

History

History
284 lines (226 loc) · 15.2 KB

README.md

File metadata and controls

284 lines (226 loc) · 15.2 KB

Ассемблер x86, часть 1

Основное чтение: Р. Э. Брайант, Д. Р. О'Халларон. Компьютерные системы: архитектура и программирование. Глава 3.

Мы будем использовать AT&T синтаксис для записи инструкций ассемблера x86. Для компиляции программ будем использовать gas, а точнее gcc.

gcc -m32 prog.S -o prog

Исходный файл с программой на ассемблере должен иметь суффикс .S (буква S должна быть заглавной!). Файл будет обрабатываться препроцессором, затем ассемблером.

На 64-битной Ubuntu может быть не установлена поддержка компиляции программ для 32-битной архитектуры i386. В этом случае даже простейшая программа, приведенная ниже, не скомпилируется. Чтобы установить 32-битные библиотеки выполните команду:

sudo apt-get install gcc-multilib

Если используется файл simpleio_i686.S или simpleio_x86_64.S, этот файл нужно просто добавить в командную строку. Например,

gcc -m32 prog.S simpleio_i386.S -o prog

Все примеры будут рассматриваться для архитектуры x86 (i386).

Регистры

x86 имеет 8 32-битных регистров общего назначения, которые называются %eax, %ebx, %ecx, %edx, %esi, %edi, %ebp, %esp. Регистр %esp - указатель стека и всегда используется в этих целях. Регистр %ebp - указатель фрейма, то есть области данных текущей функции. Он может использоваться и как регистр общего назначения, но мы этого делать не будем. Таким образом для вычислений остается 6 регистров %eax, ..., %edi. Из них регистр %eax используется для возврата 32-битного значения из функции, а пара регистров %eax, %edx - для возврата 64-битного значения. В любом случае вызываемая функция может испортить регистры %eax, %ecx, %edx, но состояние остальных регистров она обязана сохранить. Итого для хранения промежуточных результатов вычислений осталось три регистра: %ebx, %esi, %edi.

В некоторых случаях необходимо использовать младшие байты 32-битных регистров, работать с которыми можно как с 8-битовыми регистрами %al, %bl, %cl, %dl. При манипуляциях с младшим байтом остальные байты регистра не изменяются.

Инструкции

Инструкция имеет вид:

LABEL:  OPCODE  ARGS

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

OPCODE - мнемоника операции. Полный набор инструкций x86 очень велик, мы будем использовать маленькое подмножество.

ARGS - аргументы операции. У двухадресной операции операнд-приемник результата всегда пишется вторым.

Примеры:

        movl    %eax, %ebx

Переслать (присвоить, скопировать) содержимое регистра %eax' в регистр %ebx`.

        incl    %esi

Увеличить значение %esi на 1.

        subl    $4, %edi

Вычесть число 4 из значения %edi и сохранить результат обратно в %edi.

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

        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 - CF */

Операции умножения оставляют результат - 64-битное значение в паре регистров %eax (младшая часть) и %edx (старшая часть).

Вызов/возврат из подпрограмм

        call    label           /* вызов подпрограммы */
        ret                     /* возврат из подпрограммы */

Ввод-вывод

Сначала мы будем использовать для ввода-вывода маленькую библиотеку simpleio_x86.S (или simpleio_x64.S).

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

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

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

        /* фрагмент программы */
        call    readi32

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

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

        call    writei32

перед вызовом в регистр %eax должно быть помещено выводимое число.

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

        call    nl

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

        call    readi64

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

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

        call    writei64

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

Вариант библиотеки simpleio_x64.S для x64 следует стандартному соглашению о вызовах.

Арифметические флаги

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

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

Переходы

Безусловный переход выполняется с помощью инструкции 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   /* переход, если <= (беззнаковый) */

Секции исполняемого файла

        .text

Это секция кода. В ней размещается код программы и данные только на чтение.

        .data

Это секция данных. Данные в этой секции можно модифицировать

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

        .asciz  "a string"

Определяет строку, завершающуюся нулем.

        .space  SIZE, FILL

Выделяет пространство размера SIZE, заполненное байтом FILL, например,

        .space  64 * 4, 0

Выделяет место для глобального массива типа int из 64 элементов.

        .int    20

Выделяет место для глобальной 32-битной переменной с начальным значеним 20.

        .quad   -1

Выделяет место для глобальной 64-битной переменной с начальным значением -1.

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

str1:   .asciz  "Hello, there\n"

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

        movl    $str1, %esi

в этом случае адрес памяти, по которому размещается строка str1 будет загружен в регистр %esi.

Очистка и установка значения регистра

Чтобы очистить (обнулить) регистр, обычно используется инструкция xorl.

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

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

        movl    $1, %eax        // %eax = 1
        movl    $0xff, %ebx     // %ebx = 0xff
        movl    $arr, %edx      // в %edx поместить адрес, по которому размещается инструкция,
                                // помеченная addr

Непосредственный аргумент может быть первым операндом у двухадресных инструкций (addl, etc).

Дополнительное чтение:

  1. Вики-учебник "Ассемблер в Linux для программистов C".

  2. Учебное пособие для курса ВМК "Архитектура ЭВМ и язык ассемблера": часть 1, часть 2.

  3. Материалы лекций курса ВМК "Архитектура ЭВМ и язык ассемблера".