Вставки ассемблера в c visual studio

Обновлено: 21.11.2024

Некоторые компиляторы C (например, gcc) позволяют программистам смешивать встроенные ассемблерные инструкции с инструкциями C, используя блоки asm.

Например, предположим, что c1 . c8 - это инструкции C, а a1 . a6 — это инструкции на языке ассемблера, вот несколько стилей ассемблерных блоков:

Управляющие символы "\n\t" необходимы для того, чтобы каждая инструкция языка ассемблера начиналась с отдельной строки с отступом табуляции, чтобы сделать ее более читабельной.

Пример 1

Файл demo1.c содержит простую программу на C.

Давайте проанализируем команду gcc:

gcc �S �masm=intel �o demo1.s demo1.c

Обычно компиляция представляет собой конвейер из трех этапов:

Опция �S указывает на начало после этапа 2.

Опция �o говорит, что выходной файл должен называться demo1.s

Параметр masm=intel предполагает, что блоки asm будут содержать инструкции 80x86 в синтаксисе Intel, а не в синтаксисе AT&T.

Посмотрите на demo1.s. Обратите внимание, что ассемблерный блок вставляется как:

/APP
�� push eax
�� mov eax, _x
�� add eax, 1 �
�� mov _x, eax
�� всплывающее окно
/NO_APP

Обратите внимание, что после перевода глобальная переменная x переименовывается в _x, а глобальная функция main переименовывается в _main.

Обратите внимание, что я сохраняю регистры в стеке, который собираюсь использовать (в данном случае eax), а затем восстанавливаю их, когда закончу.

gcc �masm=intel �o demo1.exe demo1.c

создает исполняемый файл demo1.exe.

Полезные скрипты

Вот два полезных скрипта для компиляции и сборки:

(Примечание: на самом деле это html-файлы. Сохраняйте их как bat-файлы.)

Удалите расширение .c, чтобы использовать их:

Пример 2

Особенно полезным способом написания встроенного ассемблера является помещение ассемблерных блоков в отдельную "вспомогательную" функцию.

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

Встроенная сборка в MS Visual Studio

Microsoft Visual Studio также поддерживает встроенную сборку. На самом деле их синтаксис намного лучше. Например, ассемблерный блок в demo2.c выглядит в Visual Studio так:

Встроенная сборка на Mac

GCC, поставляемый с Mac OS, использует те же ассемблерные блоки, что и Visual Studio (см. выше). Но для компиляции вы должны включить переключатель, разрешающий встроенную сборку:

gcc — fasm-blocks — demo1 demo1.c

Чтобы запустить в оболочке BASH, вам нужно ввести:

Если это не сработает, попробуйте запустить ассемблерный блок с помощью:

".intel_syntax без префикса ������ \n\t"

Встроенная сборка в LINUX

Не ставьте символы подчеркивания перед глобальными.

Получение GNU

Если у вас Linux, расслабьтесь, gcc уже установлен.

Если у вас Windows, то есть несколько вариантов:

<р>1. Загрузите Cygwin, подобную Linux среду для Windows, которая включает оболочку bash и gcc.

<р>2. Загрузите MinGw, минималистский GNU для Windows. Это включает в себя gcc и имеет любой простой установщик.

<р>3. Загрузите djgpp, полную среду разработки для DOS/I80386, включающую gcc. Это предполагает загрузку нескольких больших файлов.

Конечно, есть и другие компиляторы C, которые поддерживают asm-блоки помимо gcc, хотя asm-блоки являются частью стандарта C.

Если вы хотите работать в DOC, выберите вариант 2 или 3, если вы хотите работать (изучить) bash, выберите вариант 1.

Существует несколько компиляторов gcc для Mac OSX. Например:

Полную документацию по коллекции компиляторов GNU можно найти по адресу:

Ключевое слово __asm ​​вызывает встроенный ассемблер и может использоваться везде, где допустимы операторы C или C++. Оно не может появиться само по себе. За ним должна следовать инструкция по сборке, группа инструкций, заключенная в фигурные скобки, или, по крайней мере, пустая пара фигурных скобок. Термин "блок __asm" здесь относится к любой инструкции или группе инструкций, независимо от того, заключены они в фигурные скобки или нет.

Поддержка Visual C++ для ключевого слова asm Standard C++ ограничена тем фактом, что компилятор не будет генерировать ошибку для ключевого слова. Однако ассемблерный блок не будет генерировать никакого осмысленного кода. Используйте __asm ​​вместо asm .

Грамматика

asm-block:
__asm ​​инструкция по сборке ; opt
__asm ​​список-инструкций-сборки > ; выбрать

список-инструкций-сборки:
инструкция-сборки ; opt
инструкция по сборке ; список-инструкций-сборки ; выбрать

Примечания

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

Поскольку ключевое слово __asm ​​является разделителем операторов, инструкции по ассемблеру можно поместить в одну строку.

До Visual Studio 2005 инструкция

при компиляции с параметром /clr не генерировался машинный код; компилятор преобразовал инструкцию в инструкцию прерывания среды CLR.

__asm ​​int 3 теперь приводит к генерации собственного кода для функции. Если вы хотите, чтобы функция вызывала точку останова в вашем коде, и если вы хотите, чтобы эта функция была скомпилирована в MSIL, используйте __debugbreak.

Для совместимости с предыдущими версиями _asm является синонимом __asm, если не указан параметр компилятора /Za (отключение языковых расширений).

Пример

Следующий фрагмент кода представляет собой простой блок __asm, заключенный в фигурные скобки:

В качестве альтернативы вы можете поставить __asm ​​перед каждой инструкцией по сборке:

Поскольку ключевое слово __asm ​​является разделителем операторов, инструкции по ассемблеру также можно поместить в ту же строку:

Все три примера генерируют один и тот же код, но первый стиль (заключение блока __asm ​​в фигурные скобки) имеет некоторые преимущества. Скобки четко отделяют ассемблерный код от кода C или C++ и позволяют избежать ненужного повторения ключевого слова __asm. Скобки также могут предотвратить двусмысленность. Если вы хотите поместить оператор C или C++ в ту же строку, что и блок __asm, вы должны заключить блок в фигурные скобки. Без фигурных скобок компилятор не может сказать, где заканчивается ассемблерный код и начинаются операторы C или C++. Наконец, поскольку текст в фигурных скобках имеет тот же формат, что и обычный текст MASM, вы можете легко вырезать и вставлять текст из существующих исходных файлов MASM.

В отличие от фигурных скобок в C и C++, фигурные скобки, окружающие блок __asm, не влияют на область видимости переменной. Вы также можете вкладывать блоки __asm; вложенность не влияет на область видимости переменной.

Эта запись блога представляет собой пошаговое руководство по вставке кода языка ассемблера x64 и x86 в проект Visual Studio C++. В этом примере я буду использовать Visual Studio 2019, Community Edition.

Для краткости я предполагаю, что читатель знаком с инструкциями языка ассемблера x64 и x86, а также с соглашениями о вызовах Windows.

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

Прежде чем мы сможем начать добавлять некоторый код на ассемблере в наш проект Visual Studio C++, я должен указать, что с момента появления процессоров x64 компилятор Microsoft больше не позволяет встроенное включение кода ассемблера с ключевым словом __asm.< /p>

Таким образом, единственный способ добавить наш код на языке ассемблера — включить его в отдельный файл(ы). Каждый файл также должен быть включен в правильную конфигурацию сборки, чтобы убедиться, что он соответствует правильному синтаксису, в зависимости от выбранной разрядности ЦП проекта C++.

Давайте рассмотрим процесс шаг за шагом:

  1. Создайте проект C++ с помощью Visual Studio.
  2. Начнем с добавления в сборку Microsoft Macro Assembler. Щелкните правой кнопкой мыши имя проекта в обозревателе решений, затем перейдите в раздел «Зависимости сборки» -> «Настройки сборки» и обязательно отметьте masm(.targets, .props) в списке и нажмите «ОК»:

Щелкните правой кнопкой мыши имя проекта в обозревателе решений, выберите «Добавить» -> «Новый элемент», затем выберите в списке «Файл заголовка» (.h) и дайте ему имя. Обязательно укажите расширение .asm. Для удобства чтения назовем этот файл asm64.asm :

В качестве теста я добавлю функцию asm_func, которая будет вызывать API MessageBox, но перед этим она вызовет нашу функцию C++ GetMsgBoxType. Итак, давайте закодируем все это:

Обратите внимание, что я использую префикс __imp_ при вызове API MessageBoxA. Прочтите этот пост в блоге, чтобы понять значение этого префикса.

Щелкните правой кнопкой мыши имя проекта в обозревателе решений, выберите «Добавить» -> «Новый элемент», затем выберите в списке «Файл заголовка» (.h) и дайте ему имя. Обязательно укажите расширение .asm. Для удобства чтения назовем этот файл asm32.asm :

Обратите внимание, что из-за соглашения о вызовах __fastcall я должен украшать имя функции asm_func префиксом и суффиксом @, а также заканчивать его количеством байтов, которые передаются в эту функцию.

Аналогичный метод применяется к вызову MessageBoxA, использующему соглашение о вызовах __stdcall. Однако в этом случае к нему добавляется _ и суффикс @, а за ним следует количество байтов, которые передаются в этот API в качестве параметров.

Кроме того, обратите внимание, что я также использовал __imp_ в этом вызове API, чтобы избежать вызова ненужного вызова JMP. Мне также пришлось удалить ведущее подчеркивание из него, чтобы учесть объявление .model C, которое будет указывать компилятору MASM добавлять _ к именам функций по умолчанию. Это зачаток устаревшего компилятора x86 MASM.

Вызов директивы OPTION LANGUAGE: SYSCALL перед объявлением функции asm_func позволяет избежать автоматического префикса имен функций с символами подчеркивания.

Это исключит 32-разрядный файл на языке ассемблера из 64-разрядной сборки конфигурации.

Это исключит 64-битный файл на языке ассемблера из 32-битной сборки конфигурации.

Обратите внимание, что я объявляю нашу функцию языка ассемблера asm_func с помощью ключевого слова extern "C". Это заставит его придерживаться схемы искажения C-имени. Кроме того, я также объявляю нашу функцию обратного вызова GetMsgBoxType с тем же ключевым словом, чтобы мы могли ссылаться на нее по имени из нашего кода сборки.

И, наконец, вот видеообзор того, что вы только что прочитали выше, с некоторыми дополнительными подробностями:

Это был краткий обзор того, как можно добавить код на языке ассемблера в ваши проекты C++, скомпилированные с помощью Microsoft Visual Studio. Обратите внимание: чтобы сделать этот пост кратким, я не стал вдаваться в особенности написания самого кода ассемблера. Для этого обратитесь к различным руководствам.

asm не является ключевым словом при использовании gcc -std=c99. Просто используйте gcc -std=gnu99, чтобы использовать C99 с расширениями GNU. Кроме того, вы можете использовать __asm__ в качестве альтернативного ключевого слова, которое работает, даже если компилятор строго придерживается стандарта.

который я нахожу более читабельным.

Я не знаю, какая у меня версия gcc, но она является частью моего Code::Block 17.12. Попробовал ваш способ, не работает.

и не может быть записано как

Я пытаюсь сделать нечто подобное с CFStringRef, но безуспешно:
CFStringRef cfstr_1 = CFSTR("A");
CFStringRef значение2;

Ниже мой код. Второй раздел кода __asm__ не компилируется. Не могу понять почему.

Спасибо за эту информативную статью.

Я создал приложение на основе диалога MFC в VS2008 в 64-разрядной версии Windows7. Я разместил ассемблерный код:

Я получаю: ошибка C2415: неправильный тип операнда. Это происходит в каждой строке, где я использовал слово «mov».

выдает ошибку:
/tmp/ccNpl4q8.s:14: Ошибка: недопустимый символ '_' в мнемонике

Пожалуйста, предложите мне аналог, который будет работать в UNIX

char msg[] = "Привет, мир!";
_asm
lea eax, msg
push eax
call printf
строка 20: добавить esp,4
>
>

Я получаю исключение
"Необработанное исключение по адресу 0x004182bc в TEST.exe: 0xC000001E: была предпринята попытка выполнить недопустимую последовательность блокировки".
в строке 20

Я думаю, что это работает в VS 2003, но не в VS 2005
Пожалуйста, помогите мне решить эту проблему.

Если вы разрабатываете действительно критичный по времени код, встроенный ассемблер также может спасти положение.

просто продолжаю эту академическую дискуссию.

какая-то сборка вам на пользу, если нечего заглядывать в окно дизассемблирования при отладке

Что касается использования встроенного ассемблера для "ускорения" кода, позвольте мне привести недавний пример из моей жизни программиста.Я пытался найти быстрый алгоритм нечеткого поиска и нашел несколько фрагментов кода для алгоритма Ratcliff/Obershelp

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

Прежде чем все бросятся изучать ассемблер, не забудьте, что части библиотеки C уже написаны на ассемблере, а некоторые реализации (например, memcpy) даже используют инструкции SIMD, если они поддерживаются вашим процессором.

Совсем недавно я столкнулся с некой "потребностью в скорости", а именно следующим образом.

Я пытался как можно быстрее просканировать файл размером в несколько сотен мегабайт на наличие и продолжение шаблона синхронизации. Данный 3-байтовый шаблон указывает на начало синхронизации, после чего файл должен содержать аналогичные шаблоны синхронизации с фиксированными смещениями. Код должен был справляться с продолжающейся потерей и усилением шаблона синхронизации — некоторые файлы могут полностью поддерживать синхронизацию, другие могут содержать несколько разрывов и повторных синхронизаций, а третьи могут вообще не содержать синхронизации. Код должен был работать быстро во всех случаях.

Моя исходная реализация считывала большие фрагменты файла в большой кольцевой буфер и выполняла поиск шаблона синхронизации в этом кольцевом буфере. Циклический буфер удобно предоставлял перегруженный оператор [], так что пользователь класса имел дело с логическими смещениями байтов, а не с физическим расположением буферизованных байтов.

При конкретном размере фрагмента файла, который я использовал в то время (возможно, 64 КБ, я не помню), каждая проверка синхронизации занимала 33 миллисекунды - не то чтобы медленно, но значительно, учитывая такие огромные файлы. Я хотел сделать намного лучше.

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

Первым шагом оптимизации было добавление в класс кольцевого буфера метода для получения необработанного непрерывного байтового буфера. Когда буферизованные данные перемещаются между концом и началом физического буфера, класс кольцевого буфера использует memcpy для изменения своего внутреннего макета. Затем код обнаружения и проверки синхронизации был изменен, чтобы использовать буфер необработанных байтов вместо того, чтобы полагаться на более затратный оператор [].

Следующим шагом было ускорение обнаружения синхронизации. Вместо проверки каждого байта с помощью roundbuffer::operator [] код был изменен для использования wcschr — версии strchr для расширенных символов. Это можно использовать для очень быстрого поиска первых двух байтов нашего шаблона. В идеале мы хотели бы использовать wcsstr для обнаружения полного 3-байтового шаблона, но эта процедура не реализована в ассемблере, как покажет пошаговое выполнение функции.

Последним шагом было ускорение проверки синхронизации: вместо трех последовательных вызовов оператора циклического буфера [] использовался memcmp().

После внесения этих изменений время обработки фрагмента сократилось с 33 миллисекунд до 1/3 миллисекунды!

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

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

Читайте также: