Assembler вместе с C++ в Visual Studio 2013

Идея этой статьи отнюдь не новая, но, поскольку мне пришлось потратить два дня на разбор всех ошибок компиляции и линковки, а также поиск ответов на свои вопросы, решил, что читатели Хабра заслуживают экономии времени. Тех, кто желает быстро узнать, как использовать одновременно *.asm и *.cpp файлы в проекте, как вызывать методы C++ из ассемблера и наоборот, прошу пожаловать под кат.

Предисловие


Началось все с прочтения мной публикации «Ассемблер для Windows используя Visual Studio» (отсюда и почти идентичный код). Там рассмотрено использование Visual Studio 2005, а для 2013-й студии процесс похожий, но есть несколько отличий, которые заставят неподготовленного пользователя долго искать решения всех проблем со сборкой.

Содержание


  1. TL;DR
  2. Создание проекта
  3. Настройка подсветки синтаксиса
  4. Тонкости вызова методов между С++ и Asm
  5. Приложение

TL;DR


Для тех, у кого совсем нет времени на прочтение: в конце статьи (в приложении) есть ссылка на готовый шаблон проекта и на аддон для подсветки синтаксиса.

Создание проекта


Иллюстрированная версия
Включаем Visual Studio, выбираем File -> New -> Project...:

image

Выбираем шаблон Win32 Console Application, кликаем ОК:

image

Жмем Next:

image

Ставим галочку напротив Empty project и жмем Finish:

image

Создаем исходники. Для этого делаем правый клик на Source Files, выбираем Add -> New Item...:

image

Выбираем C++ File и жмем Add:

image

Аналогично, создаем *.asm файл (просто меняем расширение в поле Name):

image

Важно: имена файлов должны быть разными (не учитывая расширение), иначе при создании файлов *.obj возникнет проблема перезаписи одного обьектного файла другим.

Теперь настройки. Делаем правый клик на проекте, выбираем Build Dependencies -> Build Customizations...

image

Ставим галочку напротив masm и жмем ОК:

image

Делаем правый клик на файле *.asm, выбираем Properties...:

image

В поле Item Type выбираем Microsoft Macro Assembler и жмем ОК:

image

Выбираем Project -> Properties...:

image

Выбираем Configuration Properties -> Microsoft Macro Assembler -> Listing File. В поле Assembled Code Listing File вводим $(ProjectName).lst:

image

Выбираем Configuration Properties -> Linker -> Advanced. В поле Image Has Safe Exception Handlers выбираем значение No. Жмем ОК:

image

На этом этапе проект можно считать созданным. Написание кода рассмотрено в секции Тонкости вызова методов между С++ и Asm.


Только текст
Включаем Visual Studio, выбираем File -> New -> Project....

Выбираем шаблон Win32 Console Application, кликаем ОК.

Жмем Next.

Ставим галочку напротив Empty project и жмем Finish.

Создаем исходники. Для этого делаем правый клик на Source Files, выбираем Add -> New Item....

Выбираем C++ File и жмем Add.

Аналогично, создаем *.asm файл (просто меняем расширение в поле Name).

Важно: имена файлов должны быть разными(не учитывая расширение), иначе при создании файлов *.obj возникнет проблема перезаписи одного объектного файла другим.

Теперь настройки. Делаем правый клик на проекте, выбираем Build Dependencies -> Build Customizations...

Ставим галочку напротив masm и жмем ОК.

Делаем правый клик на файле *.asm, выбираем Properties...

В поле Item Type выбираем Microsoft Macro Assembler и жмем ОК.

Выбираем Project -> Properties...

Выбираем Configuration Properties -> Microsoft Macro Assembler -> Listing File. В поле Assembled Code Listing File вводим $(ProjectName).lst.

Выбираем Configuration Properties -> Linker -> Advanced. В поле Image Has Safe Exception Handlers выбираем значение No. Жмем ОК.

На этом этапе проект можно считать созданным. Написание кода рассмотрено в секции Тонкости вызова методов между С++ и Asm.

Настройка подсветки синтаксиса


Существует аддон для Visual Studio — asmHighlighter, однако на момент написания статьи версии для VS2013 не существовало. Однако, просмотрев раздел Discussions, я нашел сообщение пользователя Trass3r, который, к счастью, поделился репозиторием с версией аддона для VS2013. После установки Visual Studio SDK мне удалось собрать проект и теперь *.vsix пакет есть в свободном доступе.

Тонкости вызова методов между С++ и Asm


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

    EXTRN  printf : proc ;we'll use printf
    

    Далее можно просто использовать call:

    ;printf(ebx,eax)
    push eax;
    push ebx
    call printf
    add esp, 8 ;pop x2
    
  2. Если же надо вызывать пользовательские методы, то кроме п.1 надо еще писать extern «C» перед определением метода.

    extern "C"
    void* readName()
    {
    	char* name = (char*)calloc(1, 255);
    	scanf("%s", name);
    	while (getchar() != '\n');
    	return name;
    }
    

    Соответственно, в *.asm файле:
    EXTRN  readName : proc ;and void* readName()
    
    и
    call readName ;eax = readName()
    

  3. В случае использования Asm-методов в C++ достаточно лишь указать прототип:

    extern "C"
    {
    	void sayHello();
    }
    

    Данный прототип соответствует такому объявлению Asm-метода:

    sayHello PROC
    
    call readName ;eax = readName()
    lea ebx, helloFormat ;ebx = &helloFormat
    
    ;printf(ebx,eax)
    push eax
    push ebx
    call printf
    add esp, 8 ;pop x2
    
    retn
    
    sayHello ENDP
    

Собственно, полный исходный код примера:

Source.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

extern "C"
{
	void sayHello();
}

void main()
{
	printf("Hello, what is your name?\n");
	sayHello();
	while (getchar() != '\n');
}
extern "C"
void* readName()
{
	char* name = (char*)calloc(1, 255);
	scanf("%s", name);
	while (getchar() != '\n');
	return name;
}


AsmSource.asm
.686
.MODEL FLAT, C
.STACK
.DATA
;-----------Local data------------------------------
helloFormat BYTE "Hello, %s!", 10, 13, 0
.CODE
;-----------External usage--------------------------
EXTRN  printf : proc;// we'll use printf
EXTRN  readName : proc;//and void* readName()
;-----------Function definitions--------------------
sayHello PROC

call readName; eax = readName()
lea ebx, helloFormat; ebx = &helloFormat

;printf(ebx,eax)
push eax
push ebx
call printf
add esp, 8;pop x2

retn

sayHello ENDP

END



Приложение


Готовый шаблон проекта можно найти здесь.
Пакет для подсветки asm синтаксиса можно найти здесь.

P.S. спасибо ilynxy за исправление «заслуживают на»)
Share post

Comments 29

    +10
    Тема выглядит не очень раскрытой.
    — как передавать параметры в функции? Как передаются различные типы (включая struct)?
    — чем отличаются конвенции fastcall и cdecl? Как ими управлять? Какие ещё есть варианты?
    — какие регистры можно «портить» в коде функции, а какие нужно сохранить?
    — как возвращается результат (опять же, для разных типов)?
    Наверное, есть и другие вопросы. Но это первое, что бросается в глаза.
      +4
      Самый главный вопрос-то пропустили: а где здесь C++?
        +2
        Была мысль спросить об устройстве таблиц виртуальных функций, но решил, что пока не стоит.
        –1
        Буду еще редактировать. Однако это не совсем то, что я хотел донести. Суть вопроса в том, чтобы «подружить» asm и cpp. Имхо, данная тема раскрыта.
        В любом случае, благодарю за тему для дальнейших исследований.
          +1
          К сожалению, «подружить» два языка, не раскрывая протокола вызова функций, написанных на одном из них, из другого языка, не очень получится. А протокол этот не очень очевидный.
        0
        На всякий случай предупрежу.
        в исходниках VirtualDub есть такой файл: a_resample_mmx.asm (этот код используется при изменении разрешения изображения).
        В этом файле несколько раз встречается такая строчка:
        and esi,byte -4
        

        Так вот, в 2010 студии (имеется ввиду компилятор ml.exe) эта строчка компилировалась правильно, а вот в 2013 — ошибочно (только что проверил на update4 — ошибка на месте).
        2013 студия компилирует эту строчку в следующие опкоды: 0152EBDC, что эквивалентно команде:
        and esi,0FFFFFFFDh  
        

        или, если записать в знаковом виде:
        and esi,byte -3
        

        Упс.
        Пришлось заменить явно на
        and esi,0FFFFFFFCh ;byte -4
        


        Повторюсь: сей баг проявляется, если использовать компилятор ассемблера ml.exe, который идет в составе Visual Studio 2013.
        • UFO just landed and posted this here
            0
            Вы внимательно читали? Это не я писал так. Это исходники VirtualDub. Вы знаете, про такую программу VirtualDub?
            • UFO just landed and posted this here
                0
                на счет опкода ошибся, да. Вроде бы правильно копировал, но нет. Прошу прощения.
                На самом деле вот так:

                Оу. Извините :) Это же адреса а не опкоды. (ушел ковырять настройки disassembler view в студии)
              +1
              Если вы считаете, что ключеове слово byte перед числом -4 правомерно превращает его в -3, объясните пожалуйста, почему я должен считать такое поведение правильным? Вполне допускаю, что мои знания ассемблера не достаточно полны.
              • UFO just landed and posted this here
                • UFO just landed and posted this here
                    0
                    byte превращается в 1 компилятором ml? Это где нибудь описано? Странно, что в 2010 студии этого не было.
                    • UFO just landed and posted this here
              +1
              А почему не что-то вроде
              int __stdcall __declspec(naked) Foo(int x, int y)
              {
                  __asm
                  {
                      pop eax
                      pop ecx
                      add eax, ecx
                      ret
                  }
              }
              
              Получаем ассемблерную функцию без пролога и эпилога и не имеем ненужных приседаний с .asm-файлами и обменом экспортами.
                +1
                Потому что в 64 битах будет
                error C4235: nonstandard extension used: '__asm' keyword not supported on this architecture
                  0
                  Можно начать использовать Clang в качестве компилятора — он поддерживает ассемблерные вставки для x64 тоже =)
                • UFO just landed and posted this here
                    0
                    А если у тебя уже есть пара мегабайт кода на MASM, который нужно переиспользовать?
                      0
                      Ну вы не путайте переиспользование и написание нового. Разные вещи.
                    0
                    В чем Вы видите смысл подключения ассемблерных модулей к программе на C++?

                    На мой взгляд, это может быть полезно исключительно для использования MMX/SSE и подобных средств. В базовой системе команд довольно сложно переплюнуть компилятор по качеству кода.
                      0
                      Смысл любой совместимости, как всегда, в переиспользовании уже имеющегося кода на MASM.
                      0
                      А 64 битный вариант проекта тоже соберется?
                        –1
                        По-моему вы просто попытались изобрести велосипед.
                        Напишите простейший helloworld с вызовом чего-нибудь вроде memcpy — и зайдите отладчиком по F11. Вы увидите, что эта функция падает в недра ассемблера.
                          +1
                          С пониманием вопроса на уровне «падает в недра ассемблера» стоило бы постесняться комментировать подобную статью.
                            0
                            С отношением на уровне «прочитал, проверять ничего не стал, сразу побежал троллить» Вам стоило бы постесняться писать комментарии на Хабре.

                            — Многие из этих низкоуровневых функций написаны на ассемблере. Со всевозможными оптимизациями под разные архитектурные особенности (вроде наличия SSE и т.д.). И при отладке вы попадаете прямо в этот текст (не автоматический дизассемблер, которая студия предлагает при отсутствии исходника модуля, а именно в ассемблерный исходник, с комментариями и т.д.). Т.е. эти функции уже реализованы дОлжным образом. И потому отдельная статья на тему, что «смотрите, так, оказывается, можно сделать!» — она да, уровня песочницы.
                              0
                              Рад, что у Вас с пониманием все же несколько лучше, чем показалось после прочтения комментария. Если хотите, чтобы Вас понимали правильно — имеет смысл выражаться яснее.

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

                              Так что Ваш комментарий, мало того, что непонятен, он еще и лишен всякого смысла.
                          0
                          Плюс за статью.
                          Проект из примера успешно компилируется также в Visual Studio 2015.
                          До этого приседал 2 дня, пытаясь слинковать MASM проект с C-шным.
                          Думаю, что эта статья спасла мне пару дней жизни.

                          Only users with full accounts can post comments. Log in, please.