Вставки ассемблера в 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++.
Давайте рассмотрим процесс шаг за шагом:
- Создайте проект C++ с помощью Visual Studio.
- Начнем с добавления в сборку 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 непосредственно в своем коде, вы ограничите свой код запуском только на процессорах, поддерживающих эти инструкции. р>
Читайте также: