fbpx

Каталог статей

Каталог статей для размещения статей информационного характера

Как выучить

Учебное пособие NASM

Учебное пособие NASM

Этот учебник покажет вам, как писать программы на языке ассемблера на архитектуре x86-64.

Вы будете писать как (1) самостоятельные программы, так и (2) программы, интегрированные с C.

Не волнуйтесь, мы не будем слишком фантазировать.

Ваша первая программа

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

Убедитесь, что установлены nasm и gcc. Сохраните одну из следующих программ как hello.asm , в зависимости от платформы вашей машины. Затем запустите программу в соответствии с приведенными инструкциями.

Если вы работаете на ОС на базе Linux:

Если вы работаете на macOS:

Структура программы NASM

NASM основан на строках. Большинство программ состоит из строк, за которыми следует одна или несколько . Строки могут иметь необязательный . За большинством строк следует ноль или более .

Как правило, код помещается в раздел .text, а постоянные данные – в раздел .data.

Подробности

    , что довольно хорошо!

Ваши первые несколько инструкций

Три вида операндов

Очень полезно знать эти понятия. Приготовьтесь запомнить их. Как вы можете запоминать? Ники Кейс поможет вам в этом!

Операнды регистров

В этом учебнике нас интересуют только регистры целых чисел, регистр флагов и регистры xmm. (Если вы знакомы с архитектурой x86, вы знаете, что это означает, что мы пропускаем регистры FP, MMX, YMM, сегментные, управляющие, отладочные, тестовые и регистры защищенного режима). Надеюсь, вы уже знакомы с архитектурой x86-84, в таком случае это будет краткий обзор. 16 целочисленных регистров имеют ширину 64 бита и называются:

(Обратите внимание, что 8 из регистров имеют альтернативные имена.) Вы можете рассматривать младшие 32 бита каждого регистра как сам регистр, но с использованием этих имен:

Младшие 16 бит каждого регистра можно рассматривать как регистр, но с использованием этих имен:

Младшие 8 бит каждого регистра можно рассматривать как регистр, но с использованием этих имен:

По историческим причинам биты с 15 по 8 из R0 … R3 названы:

И, наконец, есть 16 регистров XMM, каждый шириной 128 бит, с именами:

Изучите этот рисунок; надеюсь, он вам поможет:

Операторы памяти

  • [ число ]
  • [ reg ]
  • [ reg + reg*scale ] масштаб – 1, 2, 4 или только 8
  • [ reg + число ]
  • [ reg + reg*scale + число ].

Число называется числом ; простой регистр называется регистром ; регистр с масштабом называется регистром .

Непосредственные операнды

Они могут быть записаны различными способами. Вот несколько примеров из официальной документации.

Инструкции с двумя операндами памяти встречаются крайне редко

  • $mathtt;reg, reg$
  • $mathtt;reg, mem$
  • $mathtt;reg, imm$
  • $mathtt;mem, reg$
  • $mathtt;mem, imm$

Определение данных и резервирование пространства

Эти примеры взяты из главы 3 документации. Для размещения данных в памяти:

Существуют и другие формы; ознакомьтесь с документацией NASM. Позже.

Для резервирования места (без инициализации) можно использовать следующие псевдоинструкции. Их следует поместить в раздел

equ на самом деле не является реальной инструкцией. Она просто определяет аббревиатуру для использования самим ассемблером. (Это глубокая идея.)

Секция .bss предназначена для записываемых данных.

  • Использование библиотеки Си
  • Написание автономных программ с использованием только системных вызовов – это здорово, но редкость. Мы хотели бы использовать все лучшее, что есть в библиотеке Си.
  • Помните, как в языке C выполнение “начинается” с функции main? Это потому, что библиотека C на самом деле имеет внутри себя метку _start! Код в _start выполняет некоторую инициализацию, затем вызывает main, затем выполняет некоторую очистку, затем выдает системный вызов для выхода. Таким образом, вам просто нужно реализовать main. Мы можем сделать это на ассемблере!
  • Если у вас Linux, попробуйте это:

Под macOS это будет выглядеть немного иначе:

В стране macOS функции C (или любые функции, экспортируемые из одного модуля в другой) должны иметь префикс с подчеркиванием. Стек вызовов должен быть выровнен по 16-байтовой границе (подробнее об этом позже). А при обращении к именованным переменным требуется префикс rel.

Понимание соглашений о вызовах

Как мы узнали, что аргумент puts должен быть в RDI? Ответ: существует ряд соглашений, которые соблюдаются в отношении вызовов.

Слева направо передавайте столько параметров, сколько поместится в регистрах. Порядок, в котором выделяются регистры, следующий:

Для целых чисел и указателей: rdi , rsi , rdx , rcx , r8 , r9 .

Для плавающей точки (float, double), xmm0 , xmm1 , xmm2 , xmm3 , xmm4 , xmm5 , xmm6 , xmm7 .

Понятно? Нет? Нужно больше примеров и практики.

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

    Вот программа на языке Си, которая вызывает функцию на языке ассемблера.

    Условные инструкции

    s (знак)

    z (ноль)

    c (перенос)

    o (переполнение)

    • Условные инструкции имеют три базовые формы: j для условного перехода, cmov для условного перемещения и set для условного набора. Суффикс инструкции имеет одну из 30 форм: s ns z nz c nc o no p np pe po e ne l nl le nle g ng ge nge a na ae nae b nb be nbe .
    • Аргументы командной строки
    • Вы знаете, что в Си main – это обычная старая функция, и у нее есть несколько собственных параметров:
    • Итак, вы угадали, argc будет находиться в rdi , а argv (указатель) – в rsi . Вот программа, которая использует этот факт, чтобы просто повторять аргументы командной строки программы, по одному в строке:

    Более длинный пример

    Обратите внимание, что для библиотеки C аргументы командной строки всегда являются строками. Если вы хотите рассматривать их как целые числа, вызовите atoi . Вот изящная программа для вычисления $x^y$.

    Инструкции с плавающей точкой

    Аргументы с плавающей точкой поступают в регистры xmm. Вот простая функция для суммирования значений в двойном массиве:

    Обратите внимание, что инструкции с плавающей точкой имеют суффикс sd; это самая распространенная инструкция, но позже мы увидим и другие. Вот программа на языке Си, которая ее вызывает:

    Секции данных

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

    Рекурсия

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

    Пример вызывающей функции:

    SIMD-параллелизм

    Регистры XMM могут выполнять арифметические действия над значениями с плавающей точкой по одной операции за раз (скалярные) или по несколько операций за раз (упакованные). Операции имеют вид:

    Вот функция, которая складывает четыре плавающих числа одновременно:

    Насыщенная арифметика

    Регистры XMM также могут выполнять арифметические действия над целыми числами. Инструкции имеют вид:

    Вот пример. Он также иллюстрирует, как загружать регистры XMM. Вы не можете загружать мгновенные значения; для перемещения из памяти нужно использовать movaps. Есть и другие способы, но мы не будем рассматривать все в этом учебнике.

    Графика

    Любая программа на языке Си может быть “перенесена” на язык ассемблера. Это относится и к графическим программам.

    Эта программа, скорее всего, не работает.

    Последний раз я тестировал ее в 2003 году. Еще во времена старой школы OpenGL. Использовалась Win32. Во времена до GLSL. Использовался GLUT. У меня давно не было доступа к компьютеру с Windows, и я даже не уверен, что это будет работать. Это представлено здесь только для исторического интереса. Если вы сможете модифицировать его для работы под современным OpenGL, пожалуйста, дайте мне знать. Я обновлю программу и, конечно же, процитирую ваш вклад!

    Локальные переменные и стековые рамки

    Сначала, пожалуйста, прочитайте статью Эли Бендерски. Этот обзор более полный, чем мои краткие заметки.

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

    То при входе в функцию, $x$ будет в edi, $y$ будет в esi, а адрес возврата будет на вершине стека. Где мы можем разместить локальные переменные? Простой выбор – на самом стеке, но если у вас достаточно регистров, используйте их! Регистры в любом случае работают быстрее.

    Если вы работаете на машине со стандартным ABI, вы можете оставить rsp на месте и обращаться к “дополнительным параметрам” и локальным переменным непосредственно из rsp, например:

    nasmstructure.png

    rdx.png

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *