Как писать на ассемблере в visual studio 2019

Обновлено: 04.07.2024

В предыдущих заметках я начал писать программы, которые стартуют на ПК без операционной системы, будучи загруженными с дискеты. Программы эти я компилировал ассемблером flat assembler (FASM). В настоящей заметке мне захотелось сделать небольшое отступление и написать о том, как при помощи FASM можно писать программы пользовательского режима для Windows. Знание того, как это делается позволяет изучать программирование на ассемблере как таковое, не заморачиваясь написанием своей собственной операционной системы.

Я преподаю студентам программирование на ассемблере Intel x86 с использованием компилятора Microsoft Macro Assembler (MASM). Чтобы изучать ассемблер, нам нужно писать на нем программы, а чтобы мы могли вводить в эти программы данные и видеть какие-то результаты на экране, нам нужны какие-то средства ввода-вывода. В языках C/C++ для ввода-вывода мы привычно используем стандартные библиотеки этих языков. А в ассемблере? Есть разные варианты.

Вызываем printf из программы на MASM в Visual Studio 2015+

; MASM version of HelloWorld program using printf() function

; We have to link to these libraries since Visual Studio 2015
includelib libcmt . lib
includelib libucrt . lib
includelib libvcruntime . lib
includelib legacy_stdio_definitions . lib

. 686P ; Pentium Pro or later
. MODEL flat , stdcall
. STACK 4096

EXTERN printf : NEAR

.data
mytext BYTE "Hello World!" , 0Dh , 0Ah , 0

; "PROC" directive is mandatory here, we can't write just "main:" (don't know why)
main PROC C
push offset mytext
call printf
add esp , 4
ret
main ENDP

; it's important that we use just "END" istead of "END main"
; as we don't want to set main as the entry point.
END

В процессе отладки вышеприведенного кода я наткнулся не только на ошибки компоновщика unresolved external symbol , но и на ошибки времени выполнения. Например, стоило мне написать в конце файла END main, а не просто END, как во время выполнения программы возникала ошибка Exception thrown at 0x77621DCA (ntdll.dll) in AsmProject.exe: 0xC0000005: Access violation writing location 0x00000014 . Дело в том, что функция main в данном случае не должна быть точкой входа (директива END задает точку входа), ею должна быть функция mainCRTStartup(), которая инициализирует стандартную библиотеку языка C. Функция mainCRTStartup() в конце концов сама вызовет нашу функцию main.
Если же я пытался не использовать директиву PROC рядом с меткой main, т. е. писал вот так:

main :
push offset mytext
call printf
add esp , 4
ret

Вот еще несколько ресурсов по теме линковки программ со стандартной библиотекой языка C:

Вызываем printf из программы на flat assembler (FASM)

Ниже приведен код программы HelloWorld на FASM:

; FASM version of HelloWorld program using printf() function
format PE console
entry main
use32

; ========== CODE SECTION ==========
section '.text' code readable executable

main :
push message
call printf
add esp , 4

push 0
call exit

; ========== DATA SECTION ==========
section '.data' data readable writeable
message db "Hello, World!" , 0

; ========== IMPORT SECTION ==========
section '.idata' data import readable

; The header included below contains "library" and "import" macros that
; generate import data which must be placed in the import section of PE file
include "C:\FASM\INCLUDE\macro\import32.inc"

library msvcrt , "MSVCRT.DLL"

import msvcrt , \
imp_printf , 'printf' , \
imp_exit , 'exit'

Пояснения к программе HelloWorld на FASM

use32 заставляет компилятор генерировать 32-разрядный машинный код.

section '.data' data readable writeable секция данных, для которой разрешены чтение и запись.

Ну и наконец, я хотел бы показать, как выглядит наша программа без макросов library и import:

; FASM version of HelloWorld program using printf() function
format PE console
entry main
use32

; ========== CODE SECTION ==========
section '.text' code readable executable

; ========== DATA SECTION ==========
section '.data' data readable writeable
message db "Hello, World!" , 0

; ========== IMPORT SECTION ==========
section '.idata' data import readable

; --- array of IMAGE_IMPORT_DESCRIPTOR structures ---
dd 0 , 0 , 0 , RVA msvcrt_name , RVA msvcrt_table
dd 0 , 0 , 0 , 0 , 0
; ---

; --- array of IMAGE_THUNK_DATA structures ---
msvcrt_table :
printf dd RVA _printf
exit dd RVA _exit
dd 0
; ---

msvcrt_name db 'MSVCRT.DLL' , 0

; IMAGE_IMPORT_BY_NAME structure
_printf :
dw 0
db 'printf' , 0

; IMAGE_IMPORT_BY_NAME structure
_exit :
dw 0
db 'exit' , 0

Отладка (Debugging)

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

Заключение

Если выбирать между MASM и FASM с точки зрения обучения языку ассемблера, то я пожалуй выбираю FASM. Его можно легко установить (надо просто скачать дистрибутив с сайта). Он позволяет создавать любые двоичные файлы и помогает программисту создавать двоичные файлы форматов PE, ELF и COFF. С MASM мне пришлось помучиться, чтобы разобраться с вызовом функций из стандартной библиотеки языка C, с FASM всё оказалось куда проще.

Visual Studio включает 32-разрядную и 64-разрядную версию Microsoft Assembler (MASM) для целевого кода x64. Именованный ml64.exe — это ассемблер, который принимает язык ассемблера x64. программы командной строки MASM устанавливаются при выборе рабочей нагрузки C++ во время установки Visual Studio. Средства MASM недоступны для загрузки отдельно. инструкции по загрузке и установке копии Visual Studio см. в разделе install Visual Studio. если вы не хотите устанавливать полнофункциональную интегрированную среду разработки Visual Studio, но вам нужны только программы командной строки, скачайте средства сборки для Visual Studio.

Чтобы использовать MASM для построения кода для платформ x64 в командной строке, необходимо использовать командную строку разработчика для целевых платформ x64, которые задают требуемый путь и другие переменные среды. Сведения о запуске командной строки разработчика см. в разделе Сборка кода C/C++ в командной строке.

сведения о ml64.exe параметров командной строки см. в разделе справочник по ML и ML64 Command-Line.

Встроенный ассемблер или использование ключевого слова ASM не поддерживается для целевых объектов x64 и ARM. Чтобы перенести код x86, использующий встроенный ассемблер для x64 или ARM, можно преобразовать код в C++, использовать встроенные функции компилятора или создать файлы исходного кода на языке ассемблера. Компилятор Microsoft C++ поддерживает встроенные функции, позволяющие использовать специальные инструкции, например привилегированные, битовые сканирования и тестирования, взаимоблокировки и т. д., в качестве максимально близкого к кросс-платформенному способу. Сведения о доступных встроенных функциях см. в разделе встроенные функции компилятора.

добавление файла ассемблерного языка в проект Visual Studio C++

система проектов Visual Studio поддерживает файлы на языке ассемблера, созданные с помощью MASM в проектах C++. Вы можете создавать файлы исходного кода на языке ассемблера x64 и создавать их в объектных файлах с помощью MASM, который поддерживает 64-разрядную версию. Затем эти файлы объектов можно связать с кодом C++, созданным для целевых платформ x64. Это один из способов преодоления отсутствия встроенного ассемблера x64.

добавление файла ассемблерного языка в существующий проект Visual Studio C++

Выберите проект в обозревателе решений. в строке меню выберите Project, настройки сборки.

в строке меню выберите Project, добавить новый элемент.

Создайте код на языке ассемблера в добавленном ASM-файле. При сборке решения вызывается ассемблер MASM Assembler для сборки ASM-файла в объектный файл, который затем связывается с проектом. Чтобы упростить доступ к символам, объявите функции ассемблера как extern "C" в исходном коде C++ вместо использования соглашений об оформлении имен c++ в исходных файлах на языке ассемблера.

Директивы, относящиеся к ml64

В исходном коде на языке ассемблера, предназначенном для x64, можно использовать следующие директивы ml64:

Создание проекта консольного или оконного Windows-приложения не отличается от рассмотренного для языков программирования Си и C++.

После того, как в Visual Studio появилось окно проекта (в левой части появившегося окна отображается Обозреватель решений), для добавления нового файла программы в проект выбираем по правой кнопке мыши на папке Файлы исходного кода меню Добавить->Создать элемент.
В появившемся окне выбираем Файл C++ (.cpp), задаем имя файла и вручную добавляем к нему расширение asm. Нажимаем кнопку Добавить.

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


Далее необходимо сообщить среде разработки, что данный файл является программой на языке ассемблера, и для корректного включения его в проект требуется использовать Microsoft Macro Assembler. Для этого выбираем для проекта (по правой клавише мыши) опцию Настройки построения.

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

Microsoft Macro Assembler

В появившемся окне ставим галочку для masm (Microsoft Macro Assembler) и нажимаем OK.
Теперь нужно проверить, что для файла на языке ассемблера установился соответствующий инструмент сборки. По правой кнопке мыши для файла с расширением .asm выбираем опцию Свойства.

Свойства проекта

В появившемся окне для выбранного файла отмечаем инструмент сборки Microsoft Macro Assembler.

Инструмент сборки

Для построения проекта выбираем меню Отладка->Построить решение.

Построить решение

В случае успешного построения в нижней части окна отображается Построение: успешно 1.

Построить решение

Для запуска приложения выбираем меню Отладка->Начать отладку.

Запуск приложения на выполнение

Результат выполнения

Результат выполнения программы:

Изменить тип приложения с консольного на оконное

Изменение платформы приложения

Чтобы убрать консоль (поменять тип приложения с консольного на оконное, или наоборот) необходимо обратиться к меню Свойства проекта, вызванного по правой кнопке мыши.
В появившемся окне выбрать раздел Компоновщик->Система, и в разделе Подсистема поменять тип с Консоль на Windows (или наоборот).

Выбор консольного или оконного приложения

Запуск оконного приложения

Повторная сборка и запуск программы на выполнения выдадут следующий результат (консоли нет):

Подсветка синтаксиса языка ассемблера

Для того, чтобы включить подсветку синтаксиса языка ассемблера в Microsoft Visual Studio Express 2010 необходимо загрузить файл usertype и распаковать его в папку

C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE

Для подключения подсветки синтаксиса выбираем меню Сервис->Параметры

После перезапуска Microsoft Visual Studio Express 2010 подсветка синтаксиса языка ассемблера будет активна.

[b]Предисловие[/b]

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

[b]Содержание[/b]

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

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

[b]Создание проекта[/b]

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


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



Ставим галочку напротив 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 -> Linker -> Advanced. В поле Image Has Safe Exception Handlers выбираем значение No. Жмем ОК:


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

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

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

Ставим галочку напротив 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 -> Linker -> Advanced. В поле Image Has Safe Exception Handlers выбираем значение No. Жмем ОК.

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

[b]Настройка подсветки синтаксиса[/b]

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

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

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

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

Соответственно, в *.asm файле:

EXTRN readName : proc ;and void* readName()
и

call readName ;eax = readName()

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

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

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

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

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

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

Поскольку мне всё равно пришлось ставить Visual Studio Community для того, чтобы установить Windows Kits для работы с WinAPI, то я решил не использовать MingW, а доустановить C++ build tools и использовать их для компиляции. В этом случае придётся переделать задачи (tasks) и настройки VSCode.

Хорошее описание нашёл здесь, его и буду использовать в данной заметке.

Нам потребуется


1. Естественно нам потребуется сама программа VSCode.
2. В Visual Studio Community должен быть установлен компонент Desktop development with C++ :

Чтобы проверить успешную установку, достаточно вызвать Developer Command Prompt for VS 2019 (файл VsDevCmd.bat ) из Пуска. Там нужно запустить файл cl.exe . Вывод консоли должен быть без ошибок:

3. Для VSCode должно быть установлено дополнение (расширение) Microsoft C/C++

Настройка

4. В Проводнике открываем рабочую папку проекта и, удерживая Shift , нажимаем правую кнопку мыши, после чего выбираем Open PowerShell window here
5. В открывшемся окошке PowerShell запускаем VSCode, для этого нужно набрать code . и нажать Enter :

Шаги 9-11 скорее всего не нужны. В статье они есть, но без них у меня всё тоже прекрасно компилируется.


9. Открываем палитру команд с помощью комбинации клавиш Ctrl + Shift + P
10. Список большой, поэтому проще ввести часть слова и выбрать нужную команду Edit Configurations UI из списка:

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



12. Внесём изменения в файл settings.json :

У меня глобальный файл настроек, а не только для проекта, поэтому я добавляю строчки в начало файла C:\Users\Denis\AppData\Roaming\Code\User\settings.json

13. Ранее я уже создавал файл Задач tasks.json, поэтому сейчас я добавлю к нему новые строчки:

14. Чтобы у нас была возможность запустить проект на отладку, можно использовать файл launch.json . Но я не хочу создавать такой файл для каждого проекта каждый раз, поэтому сделаю глобальную конфигурацию. Для этого я добавлю строчки в файл settings.json :

Благодаря этому, при нажатии F5 , проект будет откомпилирован, а потом запущен сразу после этого. Просто запустить, без отладки, можно комбинацией Ctrl + F5

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