Как вставить ассемблер в с
Ассемблер в Dev-C++
Давно хотел разобраться с этой темой. И вот наконец собрался.
Дело в том, что инструкции процессора Интел и синтаксис вставок ассемблерного кода в программы на Visual C++ не будут работать в Dev-C++.
Потому что Dev-C++ использует компилятор GCC (бесплатный компилятор языка С++). Этот компилятор имеет встроенный ассемблер, но это не MASM и не TASM с привычным набором команд. Это ассемблер AT&T, синтаксис которого очень сильно отличается от синтаксиса MASM/TASM и подобных.
Я сначала никак не мог понять, почему. Но когда немного познакомился с документацией, то понял.
Оказывается, в компиляторе GCC, как и в Паскале и в Visual C++, есть ключевые слова asm и __asm. Вот только это вовсе не операторные скобки.
По сути это функции, которые вызываются с определённым набором параметров. И в эти функции в качестве параметров передаются инструкции ассемблера!
А здесь я просто в самых общих чертах покажу, как можно использовать вставки на ассемблере в Dev-C++ (это будет также справедливо для других средств разработки, использующих компилятор GCC).
Ассемблер AT&T
Как я уже сказал, этот ассемблер сильно отличается от привычных нам инструкций процессора Интел. Здесь я не буду об этом говорить. Если кому интересно, то основные отличия описаны здесь.
Вставка на ассемблере в Dev-C++
Основной формат вставки кода ассемблера показан ниже:
asm( «Здесь код на ассемблере»);
Как вы могли заметить, здесь используются два варианта встраивания ассемблера: asm и __asm__. Оба варианта правильные. Следует использовать __asm__, если ключевое слово asm конфликтует с каким-либо участком вашей программы (например, в вашей программе есть переменная с именем asm).
Если встраивание кода на ассемблере содержит более одной инструкции, то мы пишем по одной инструкции в строке в двойных кавычках, а также суффикс ’\n’ и ’\t’ для каждой инструкции.
Однако в большинстве случаев требуется обмен данными между кодом на ассемблере и переменными, которые объявлены в исходных кодах на языке высокого уровня.
Это тоже возможно. Общий формат ассемблерной вставки для компилятора GCC такой:
Не буду здесь подробно всё это расписывать, так как это уже сделано здесь. Там же вы найдёте все подробности использования встроенного ассемблера компилятора GCC (ну хотя не все, а основные).
Я же здесь приведу пример, и на этом успокоюсь.
Для начала не очень хороший пример.
Не очень хороший он потому, что мы изменяем значения регистров, а потом получаем значение регистра eax в переменную х. Но при этом мы не заботимся о том, что состояние этих регистров может быть изменено где-то в другом месте. Так что это может привести к потенциальным неожиданностям.
Теперь попробуем сделать всё чуть более правильно (хотя и не идеально).
Здесь в ассемблерный код мы передаём значения переменных y и z. Значение у помещается в регистр еах (на это указывает буква “a”), а значение z помещается в регистр ebx (на это указывает буква “b”).
Ну вот как-то так. Это, конечно, в самых общих чертах. Если кого интересуют подробности, то см. здесь.
Ассемблерная вставка в Си
Пытаюсь разобраться, как вставить код на ассемблере в Си код. Беглый поиск по гугл лишь запутал. Попытка что-то скомпилировать из написанного не увенчалась успехом. Может ли кто-нибудь мне привести два варианта рабочего кода, для linux/windows (x64), который бы выражал следующие идеи:
п.с. Я правильно понимаю, что для этих целей нужно использовать GAS? Нет никакой возможности заставить компилятор понимать вставки на NASM?
2 ответа 2
По-моему, более правильный путь использования ассемблера в Си, не вставками и смешиванием Си и ассемблера, а линковкой скомпилированного ассемблера.
Т.е. пишите на любимом NASM (к примеру) в файл test.asm :
Далее, вызываете эту функцию из Си:
При сборке не забывайте указать линковщику, чтобы он линковался с test.o :
Чтобы собрать тоже самое под Linux, вам нужно будет привести ассемблерный код в соответствие с соглашением о передаче параметров в Unix 64-bit (отличается от соглашения для Win64) и указать NASM другой выходной формат:
О соглашениях передачи параметров и об особенностях написания 64-битного кода, в NASM посвящена отдельная глава в документации: Writing 64-bit Code (Unix, Win64)
Синтаксис оператора следующий:
Текст вставки представляет собой строковую константу с ассемблерными инструкциями. В нем могут находиться не только ассемблерные инструкции, но и любые директивы ассемблера GAS.
Операнд имеет следующий вид:
имя_переменной — ни что иное, как имя C-переменой, значение которой вы хотите использовать в ассемблерном коде.
ограничение_типа — строковая константа, описывает допустимый тип операнда.
Итак, этого достаточно, чтобы написать ваш пример:
В общем-то писал по этому документу. Неплохая информация представлена здесь.
Связь ассемблера с языками высокого уровня
Существуют следующие формы комбинирования программ на языках высокого уровня с ассемблером:
Встроенный ассемблер
При написании ассемблерных вставок используется следующий синтаксис:
КодОперации задает команду ассемблера,
операнды – это операнды команды.
В конце записывается ;, как и в любой команде языка Си.
Комментарии записываются в той форме, которая принята для языка Си.
Если требуется в текст программы на языке Си вставить несколько идущих подряд команд ассемблера, то их объединяют в блок:
Внутри блока текст программы пишется с использованием синтаксиса ассемблера, при необходимости можно использовать метки и идентификаторы. Комментарии в этом случае можно записывать как после ;, так и после //.
Использование внешних процедур
Для связи посредством внешних процедур создается многофайловая программа. При этом в общем случае возможны два варианта вызова:
Соглашение | Параметры | Очистка стека | Регистры |
Pascal (конвенция языка Паскаль) | Слева направо | Процедура | Нет |
C (конвенция С) | Справа налево | Вызывающая программа | Нет |
Fastcall (быстрый или регистровый вызов) | Слева направо | Процедура | Задействованы три регистра (EAX,EDX,ECX), далее стек |
Stdcall (стандартный вызов) | Справа налево | Процедура | Нет |
Конвенция Pascal заключается в том, что параметры из программы на языке высокого уровня передаются в стеке и возвращаются в регистре АХ/ЕАХ, — это способ, принятый в языке PASCAL (а также в BASIC, FORTRAN, ADA, OBERON, MODULA2), — просто поместить параметры в стек в естественном порядке. В этом случае запись
Конвенция С используется, в первую очередь, в языках С и C++, а также в PROLOG и других. Параметры помещаются в стек в обратном порядке, и, в противоположность PASCAL-конвенции, удаление параметров из стека выполняет вызывающая процедура.
Запись some_proc(a,b,c,d)
будет выглядеть как
Вызванная таким образом процедура может инициализироваться так:
Трансляторы ассемблера поддерживают и такой формат вызова при помощи полной формы директивы proc с указанием языка С:
В случае если стек был задействован, освобождение его возлагается на вызываемую процедуру.
В случае быстрого вызова транслятор Си добавляет к имени значок @ спереди, что искажает имена при обращении к ним в ассемблерном модуле.
Возврат результата из процедуры
Чтобы возвратить результат в программу на С из процедуры на ассемблере, перед возвратом управления в вызываемой процедуре (на языке ассемблера) необходимо поместить результат в соответствующий регистр:
Тип возвращаемого значения | Регистр |
unsigned char | al |
char | al |
unsigned short | ax |
short | ax |
unsigned int | eax |
int | eax |
unsigned long int | edx:eax |
long int | edx:eax |
Пример Умножить на 2 первый элемент массива (нумерация элементов ведется с 0).
Чтобы построить проект в Microsoft Visual Studio Express 2010, совместив в нем файлы, написанные на разных языках программирования, выполняем следующие действия.
Добавляем в дерево проекта два файла исходного кода:
Второй добавляемый файл исходного кода будет иметь расширение .asm, которое необходимо указать явно.
Важно, чтобы файлы программ на C++ и ассемблере имели не только разные расширения, но и имена. В случае совпадающих имен файлов возникнет ошибка при компоновке проекта, поскольку оба файла будут иметь одно и то же имя объектного файла.
Набираем код программы для файлов вызывающей и вызываемой процедур соответственно на C++ и ассемблере.
Подключаем инструмент Microsoft Macro Assembler. По правой кнопке мыши для проекта выбираем Настройки построения.
В появившемся окне ставим галочку в строке masm.
Результат построения отображается в нижней части окна проекта.
Работа с аргументами вещественного типа
При вызове функции с аргументами вещественного типа конфигурация проекта ничем не отличается от описанной выше. Для передачи аргументов необходимо указать их тип.
тип Си | Количество байт | Тип аргумента ассемблера |
float | 4 | dword |
double | 8 | qword |
Возвращаемое вещественное значение по умолчанию располагается в вершине стека сопроцессора st(0).