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 за исправление «заслуживают на»)

Комментарии 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.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Вы внимательно читали? Это не я писал так. Это исходники VirtualDub. Вы знаете, про такую программу VirtualDub?
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                на счет опкода ошибся, да. Вроде бы правильно копировал, но нет. Прошу прощения.
                На самом деле вот так:

                Оу. Извините :) Это же адреса а не опкоды. (ушел ковырять настройки disassembler view в студии)
              +1
              Если вы считаете, что ключеове слово byte перед числом -4 правомерно превращает его в -3, объясните пожалуйста, почему я должен считать такое поведение правильным? Вполне допускаю, что мои знания ассемблера не достаточно полны.
              • НЛО прилетело и опубликовало эту надпись здесь
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    byte превращается в 1 компилятором ml? Это где нибудь описано? Странно, что в 2010 студии этого не было.
                    • НЛО прилетело и опубликовало эту надпись здесь
              +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 тоже =)
                • НЛО прилетело и опубликовало эту надпись здесь
                    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-шным.
                          Думаю, что эта статья спасла мне пару дней жизни.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое