Вставка ассемблера в c. Ассемблер для Windows используя Visual Studio. Ограничения на встроенное ассемблирование

Вставка ассемблера в c. Ассемблер для Windows используя Visual Studio. Ограничения на встроенное ассемблирование

высокого уровня

Несмотря на огромные возможности языков высокого уровня, иногда возникает необходимость применения Ассемблера в программах на ЯВУ. Наиболее распространены два подхода:

1) в тексте программы, написанной на языке высокого уровня, делаются ассемблерные вставки на встроенном ассемблере;

2) подключаются внешние ассемблерные модули: файл с процедурами пишется на внешнем ассемблере, компилируется в объектный файл OBJ, который подключается к проекту на ЯВУ.

Ассемблерные вставки применяются:

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

Для вызова команд, не используемых компилятором ЯВУ. С появлением новых поколений процессоров добавляются новые, более эффективные, машинные команды. Однако компиляторы ЯВУ при переводе программы с языка высокого уровня в машинные коды стараются использовать устаревшие команды i386-го, чтобы обеспечить максимальную совместимость со всеми, даже старыми, компьютерами. А если нужны специализированные команды новых процессоров (MMX, XMM, SSE, SSE II), приходится писать на ассемблере.

Область применения ассемблерных вставок ограничена возможностями компилятора. Например, во встроенном ассемблере запрещается вызывать некоторые привилегированные команды; «неизвестные» компилятору команды, появившиеся уже после его выпуска. В то время как использование внешнего ассемблера не имеет ограничений.

3.3. Встроенный ассемблер

Системы программирования Delphi, C++ Builder, Visual C++ позволяют вставлять в текст программы на языке высокого уровня участки кода, написанные на ассемблере. Этот ассемблер называется встроенным, и имеет незначительные синтаксические отличия от ассемблера TASM или MASM. Подробнее про него можно прочитать в справочной системе ЯВУ. Здесь приводятся краткие сведения.

    C ++ Builder, Visual C++ :

Ассемблерный текст заключается в блок _asm{…}

cmp eax, SomeVariable

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

    Delphi :

Ассемблерные команды заключаются между словами asm end ;

cmp eax, SomeVariable

Внутри ассемблерного блока можно обращаться по именам к переменным, функциям, процедурам и меткам. Переменные, объявленные внутри блока директивами DB, DW и т.п. будут размещены в сегменте кода, а не данных. Это нужно учесть, чтобы компилятор не стал исполнять их значения как машинные коды – это может привести к ошибке исполнения программы.

Если внутри ассемблерного блока нужны переходы по меткам, их делают локальными – имена таких меток следует начинать с символа @. Область действия локальной метки ограничена ассемблерным блоком:

test SomeVariable , 0Fh

... // какие-то команды

@M1: // это локальная метка

3.4. Подключение внешних ассемблерных модулей в программы на языках высокого уровня

Этап 1 . Создание ассемблерного файла с экспортируемой процедурой. Чтобы подключение ассемблерного модуля к программе на ЯВУ было корректным, он должен удовлетворять правилам, в соответствии с которыми создает программу компилятор ЯВУ. Так, должны совпадать имена сегментов, конвенции вызова и т.д. Каркас ассемблерного модуля для подключения к Delphi, C++ Builder и Visual C++, приведен в листинге 1.

Листинг 1 – Каркас ассемблерного модуля для подключения к программам Win32.

.486 ; 32-разрядные приложения

. model flat ; в программах Win32 используется линейная модель

; памяти (flat)

. bss

; в этом сегменте описываются неинициализированные данные

. const

; в этом сегменте описываются типизированные константы

. data

; в этом сегменте описываются переменные с начальными значениями

. code

PUBLIC Имя_процедуры ; чтобы процедуру можно было вызывать из

; программы на ЯВУ, её нужно объявить экспортируемой

; реализация процедуры

Имя_процедуры proc near ; все процедуры – ближние

push ebp ; если процедура с параметрами, то в начале процедуры

; регистр

mov ebp , esp ; затем установить ebp = esp для обращения к параметрам

; здесь тело процедуры

pop ebp ; в конце восстанавливаем ebp

ret N ; если процедура освобождает стек из-под параметров сама, то

; N – число байтов, которое занимает стековый кадр,

; N всегда кратно 4.

Имя_процедуры endp

end ; конец модуля

Директива PUBLIC применяется, чтобы сделать имя процедуры «видимой» за пределами модуля, чтобы её «нашел» компилятор ЯВУ.

Компиляторы С++ различают в именах функций большие и маленькие буквы. Delphi, обычно не чувствительная к регистру, при импорте процедур из внешних OBJ и DLL файлов, буквы большие и малые различает. Поэтому при объявлении процедуры в ассемблерном модуле её имя следует писать так, как оно будет вызываться в ЯВУ.

Замечания по написанию тела процедуры – в п.3.5.

Этап 2 . Компиляция ассемблерного модуля в файл OBJ.

При компиляции следует использовать ключ /ml , чтобы заглавные и строчные буквы различались:

TASM.EXE /ml имя_файла .asm

Если этого не сделать, то процедуру MyProc язык высокого уровня увидит как MYPROC , поскольку TASM по умолчанию все экспортируемые имена записывает заглавными буквами.

Этап 3 . Подключение объектного файла к программе на ЯВУ.

!!! Предварительно объектный файл скопируйте в папку с проектом программы на ЯВУ.

    C ++ Builder :

Объектный модуль можно подключить тремя способами:

1) добавить к проекту через меню Project / Add To Project, тип файлов "OBJ".

2) директивой

#pragma link "имя_файла.obj"

3) директивой

USEOBJ("имя_файла.obj")

    Visual C++

В VC++ 6.0 имя OBJ-файла нужно вписать в параметры командной строки компоновщика: для этого выбрать меню Project / Settings, открыть закладку Link и вписать имя OBJ-файла в параметры командной строки “Object/Library modules”.

В VC++.NET выбирается меню Project/Properties, в дереве свойств папка C/C++ → Command Line и имя OBJ-файла вписывается в строку “Addition options”.

    Delphi :

Для подключения объектного файла в начало программы (между строками Program … или Unit … и строкой Uses …) добавляется директива:

{$LINK имя_файла.obj}

или {$L имя_файла.obj}

Этап 4 . Объявление в программе импортируемой процедуры.

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

    C ++ Builder , Visual C++ :

Заголовок (прототип) ассемблерной функции объявляется с квалификаторами extern C ” :

extern "C" int __stdcall MyFunc(int x,y);

Если внешних функций несколько, их можно перечислить внутри секции extern :

extern "C"{

int __stdcall MyFunc(int x,y);

int __cdecl YourFunc(void* param);

Опция extern указывает компилятору, что тело функции следует искать в объектных файлах или библиотеках. Квалификатор "С" нужен, чтобы компилятор С++ не искажал имена функций (иначе он «от-себя» добавляет в имя @, подчеркивания и пр., и в таком виде пытается найти имя в подключенных модулях).

    Delphi :

В секции описания процедур и функций помещается заголовок ассемблерной подпрограммы, а вместо тела пишется зарезервированное слово external . Так компилятор понимает, что тело подпрограммы нужно искать во внешнем OBJ-файле. Например

Procedure MyProc(X,Y:integer); stdcall; external ;

Function MyFunc(X,Y:integer):integer; cdecl; external ;

Если предполагается, что внешняя подпрограмма имеет переменное количество аргументов, то записывается директива varargs . Эту директиву можно использоваться только совместно с конвенцией cdecl .

Что такое Ассемблер

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

Разработка программ на ассемблере - очень тяжёлая штука. Взамен потраченному времени вы получаете эффективную программу. Программы на ассемблере пишут, когда важен каждый такт процессора. На ассемблере вы даёте конкретные команды процессору и никакого лишнего мусора. Этим и достигается высокая скорость выполнения вашей программы.

Чтобы грамотно использовать ассемблер необходимо знать программную модель микропроцессорной системы. С точки зрения программиста микропроцессорная система состоит из:

  1. Микропроцессора
  2. Памяти
  3. Устройств ввода/вывода.

Программная модель хорошо описана в литературе .

Синтаксис Ассемблера

Общий формат строки программы на ассемблере

<Метка>: <Оператор> <Операнды> ; <Комментарий>

Поле метки. Метка может состоять из символов и знаков подчёркивания. Метки используются в операциях условного и безусловного перехода.

Поле оператора. В этом поле содержится мнемоника команды. Например мнемоника mov

Поле операндов. Операнды могут присутствовать только если присутствует Оператор (поле оператора). Операндов может не быть, а может быть несколько. Операндами могут быть данные, над которыми необходимо выполнить какие-то действия (переслать, сложить и т.д.).

Поле комментария. Комментарий нужен для словесного сопровождения программы. Всё, что стоит за символом ; считается комментарием.

Первая программа на языке Ассемблера

В этой статье будет использоваться ассемблер для i80x86 процессора и использоваться следующее программное обеспечение:

  • TASM - Borland Turbo Assembler - компилятор
  • TLINK - Borland Turbo Linker - редактор связей (компоновщик)

Если быть конкретным, то Tasm 2.0.

По традиции наша первая программа будет выводить строку "Hello world!" на экран.

Файл sample.asm

Model small ; Модель памяти.stack 100h ; Установка размера стека.data ; Начало сегмента данных программы HelloMsg DB "Hello World!",13,10,"$" .code ; Начало сегмента кода mov ax,@DATA ; Пересылаем адрес сегмента данных в регистр AX mov ds,ax ; Установка регистра DS на сегмент данных mov ah,09h ; DOS функция вывода строки на экран mov dx,offset HelloMsg ; Задаём смещение к началу строки int 21h ; Выводим строку mov ax,4C00h ; DOS функция выхода из программы int 21h ; Выход из программы end

Как вы могли заметить, что программа разделена на сегменты: сегмент данных, сегмент кода и есть ещё стековый сегмент.

Рассмотрим всё по порядку.

Директива.model small задаёт модель памяти. Модель small - это 1 сегмент для кода, 1 сегмент для данных и стека т.е. данные и стек находятся в одном сегменте. Бывают и другие модели памяти, например: tiny, medium, compact. В зависимости от выбранной вами модели памяти сегменты вашей программы могут перекрываться или могут иметь отдельные сегменты в памяти.

Директива.stack 100h задаёт размер стека. Стек необходим для сохранения некоторой информации с последующим её восстановлением. В частности стек используется при прерываниях. В этом случае содержимое регистра флагов FLAGS, регистра CS и регистра IP сохраняются в стеке. Далее идёт выполнение прерывающей программы, а потом идёт восстановление значений этих регистров.

  • Регистр флагов FLAGS содержит признаки, которые формируются после выполнения команды процессором.
  • Регистр CS (Code Segment) содержит адрес сегмента кода.
  • Регистр IP (Instruction Pointer) - указатель команд. Он содержит адрес команды, которая должная выполниться следующей (Адрес относительно сегмента кода CS).

Более подробное описание выходит за рамки простой статьи.

Директива.data определяет начало сегмента данных вашей программы. В сегменте данных определяются "переменные" т.е. идёт резервирование памяти под необходимые данные. После.data идёт строка
HelloMsg DB "Hello World!",13,10,"$"

Здесь HelloMsg - это символьное имя, которое соответствует началу строки "Hello World!" (без кавычек). То есть это адрес первого символа нашей строки относительно сегмента данных. Директива DB (Define Byte) определяет область памяти доступную по-байтно. 13,10 - коды символов Новая строка и Возврат каретки, а символ $ необходим для корректной работы DOS функции 09h. Итак, наша строка будет занимать в памяти 15 байт.

Директива.code определяет начало сегмента кода (CS - Code Segment) программы. Далее идут строки программы содержащие мнемоники команд.

Расскажу о команде mov.

mov <приёмник>, <источник>

Команда mov - команда пересылки. Она пересылает содержимое источника в приёмник. Пересылки могут быть регистр-регистр, регистр-память, память-регистр, а вот пересылки память-память нет т.е. всё проходит через регистры процессора.

Чтобы работать с данными необходимо настроить регистр сегмента данных. Настройка состоит в том, что мы записываем адрес сегмента данных @DATA в регистр DS (Data Segment). Непосредственно записать адрес в этот регистр нельзя - такова архитектура, поэтому мы используем регистр AX. В AX мы записываем адрес сегмента кода

а потом пересылаем содержимое регистра AX в регистр DS.

После этого регистр DS будет содержать адрес начала сегмента данных. По адресу DS:0000h будет содержаться символ H. Я предполагаю, что вы знаете о сегментах и смещениях.

Адрес состоит из двух составляющих <Сегмент>:<Смещение>, где Сегмент это 2 байта и смещение - 2 байта. Получается 4 байта для доступа к любой ячейке памяти.

mov ah,09h
mov dx,offset HelloMsg
int 21h

Тут мы в регистр AH записываем число 09h - номер функции 21-го прерывания, которая выводит строку на экран.

В следующей строке мы в регистр DX записываем адрес(смущение) к началу нашей строки.

Далее мы вызываем прерывание 21h - это прерывание функций DOS. Прерывание - когда выполняющаяся программа прерывается и начинает выполнятся прерывающая программа. По номеру прерывания определяется адрес подпрограммы DOS, которая выводит строку символов на экран.

У вас наверняка возникнет вопрос: А почему мы записываем номер функции 09h в регистр AH ? И почему смещение к строке записываем в регистр DX ?
Ответ простой: для каждой функции определены конкретные регистры, которые содержат входные данные для этой функции. Посмотреть какие регистры нужны конкретным функциям вы можете в help"е.

mov ax,4C00h
int 21h

mov ax,4C00h - пересылаем номер функции в регистр AX. Функция 4C00h - выход из программы.

int 21h - выполняем прерывание (собственно выходим)

end - конец программы.

После директивы end компилятор всё игнорирует, поэтому можете там писать всё, что угодно:)

Если вы дочитали до конца, то вы герой!

Майко Г.В. Ассемблер для IBM PC: - М.: "Бизнес-Информ", "Сирин" 1999 г. - 212 с.

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

Мы рассмотрим две возможности стыковки Си и Ассемблера: использование команд на языке Ассемблера прямо в тексте программы, написанной на языке Си, и вызов из программы на языке Си подпрограммы, написанной на языке ассемблера.

Встроенный ассемблерный код.

Рассмотрим самый простой пример

#include

Void main()

{ int TestValue;

printf("Input TestValue\n");

scanf("%d", &TestValue);

asm inc word ptr TestValue

printf("Incremented %d\n",TestValue);

Ключевое слово asmозначает, что за ней следует строка на языке Ассемблера. Точку с запятой - разделитель операторов в языке Си - ставить не нужно. Вызывает удивление присутствие атрибутного оператораword ptr . Зачем он нужен, если в тексте программы указано, что TestValue имеет типint.

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

Ключ -BозначаетCompileviaassemble- компиляция посредством ассемблирования. Файлincr.cпреобразуется во временный файлincr.asm. Далееtccвызывает ассемблерtasm.exe, который создаёт объектный файл. Далее вызывается компоновщик.tccдолжен знать, где находитсяtasm. Поэтому, если кtasm.exeне "проложено дорожки" (path), то её нужно явно указать в файлеturboc.cfg, расположенном в текущей директории. Для нашего ВЦ этот файл должен быть таким

Вместо ключа -Bможно было вставить в текст программы в качестве первой строки директиву #pragmainline.

Как посмотреть сгенерированный ассемблерный код. Для этого укажем ключ -S - produce assemble output.

Тогда на диске создаётся файл incr.asm. В нём находим строку

inc word ptr

Переменная TestValueсоздаётся в автоматической памяти, т.е. в стеке. Как мы видели ранее, такие переменные адресуются с помощьюbp, причём отсчёт идёт в сторону уменьшения адресов. В приведенной выше команде атрибутный оператор необходим, т.к. неясно, на что ссылаетсяbp-2 - на слово или байт.

Ключ -Sполезен для изучения ассемблерного аналога исходного текста на языке Си. Но можно обойтись и без него.

В BorlandC++ 3.1 появился встроенный (built-in) ассемблер. Если не указать ключ -Bпри вызовеbcc, то используется именно он. Встроенный ассемблер не использует макросов, режимаIDEAL, инструкций 386-го процессора (впрочем, уже естьBorlandC++ 5.01).

Начиная с BorlandC++ 3.1 можно заключать группу ассемблерных команд в фигурные скобки и помещать перед ними ключевое словоasm.

Ограничения на встроенное ассемблирование.

    Команды перехода могут ссылаться только на метки Си

    Остальные команды могут иметь любые операнды кроме меток Си

    В начале ассемблерного фрагмента нужно сохранять, а в конце восстанавливать регистры BP,SP,CS,DS,SS(разумеется, если они претерпевают изменения). Если возникают сомнения, полезно использовать ключ -Sи смотреть ассемблерный код в целом.

Недостатки встроенного ассемблерного кода

    компилятор не оптимизирует код текста программы на Си,

    нет мобильности (нельзя перенести программу на другой тип процессора),

    медленнее выполняется компиляция,

    затруднена отладка.

В VisualC++ 6.0 используется ключевое слово__ asm (обратите внимание, чтоasm предшествует два символа подчёркивания).

6 ответов

Вы можете получить доступ к переменным по их имени и скопировать их в регистры. Вот пример из MSDN:

Int power2(int num, int power) { __asm { mov eax, num ; Get first argument mov ecx, power ; Get second argument shl eax, cl ; EAX = EAX * (2 to the power of CL) } // Return with result in EAX }

Компилятор microsoft очень плохо оптимизируется при подключении встроенной сборки. Он должен создавать резервные копии регистров, потому что если вы используете eax, тогда он не переместит eax в другой свободный регистр, он будет продолжать использовать eax. Ассемблер GCC намного продвинулся вперед на этом фронте.

Чтобы обойти это, Microsoft предложила intrinsics . Это гораздо лучший способ сделать вашу оптимизацию, поскольку она позволяет компилятору работать с вами. Поскольку Крис упоминал, что встроенная сборка не работает под x64 с компилятором MS, поэтому на этой платформе вы ДЕЙСТВИТЕЛЬНО лучше просто используете встроенные средства.

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

В реестрах ничего нет. как выполняется блок _asm. Вам нужно переместить материал в регистры. Если есть переменная: "a", тогда вам нужно

Asm { mov eax, [a] }

Стоит отметить, что VS2010 поставляется с ассемблером Microsoft. Щелкните правой кнопкой мыши по проекту, перейдите к правилам сборки и включите правила сборки ассемблера, а среда IDE обработает файлы.asm.

это несколько лучшее решение, так как VS2010 поддерживает 32-битные и 64-битные проекты, а ключевое слово __asm ​​НЕ работает в 64-битных сборках. Вы ДОЛЖНЫ использовать внешний ассемблер для 64-битного кода:/

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

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

Если вам нужно вставить встроенную сборку для большой функции, создайте новую функцию для кода, который необходим для встроенного. Снова замените на С++ или сборку во время сборки.

Это мои предложения, ваш пробег май-Вар (YMMV).

Мне очень нравится сборка, поэтому я не собираюсь быть здесь. Похоже, что вы профилировали свой код и нашли "горячую точку", что является правильным способом запуска. Я также предполагаю, что 200 строк, о которых идет речь, не используют много конструкций высокого уровня, таких как vector .

Все, что сказал: если бы я был вами, я бы прошел через этот код в отладчике VS, используя представление "Разборка". Если вам комфортно читать код, когда вы идете вперед, это хороший знак. После этого выполните компиляцию Release (Debug отключит оптимизацию) и создайте список ASM для этого модуля. Тогда, если вы думаете, что видите место для улучшения... у вас есть место для начала. Ответы других людей связаны с документацией MSDN, которая действительно очень скудная, но все же разумное начало.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows