Ассемблер MASM позволяет использовать гибридный подход, при котором часть программы пишется на ассемблере, а часть - на С/С++. Это позволяет сократить и упростить программу. Например, определим файл app.c с кодом на языке C:
А в файле app.c определим следующий код:
#include <stdio.h> // подключаем внешнюю функцию hello из файла hello.asm extern void hello(void); int main() { printf("Hello starts\n"); hello(); printf("Hello ends\n"); }
Это обычная программа на языке С, в которой с помощью оператора extern подключаем некоторую внешнюю функцию hello, которая ничего не возвращает и не принимает никаких параметров. В функции main вызываем эту внешнюю функцию hello.
Теперь в той же папке определим файл hello.asm, который будет содержать следующий код на ассемблере:
.code option casemap:none ; определяем процедуру hello public hello hello proc ret ; возвращаемся в вызывающий код hello endp end
Секция .code
здесь начинается с директивы option
option casemap:none
Директива option
указывает MASM сделать все символы чувствительными к регистру. Это необходимо, потому что MASM по умолчанию нечувствителен к регистру и
отображает все идентификаторы в верхнем регистре (поэтому hello() станет HELLO()). Но язык C чувствителен к регистру и рассматривает hello() и HELLO() как два разных идентификатора.
Далее идет определение функции hello. С помощью оператора public указываем, что функция hello будет видна вне исходного/объектного файла MASM:
public hello
Без этого оператора функция hello() была бы доступна только внутри текущего файла, и при компиляции компилятор не смог бы ее найти.
Затем между операторами hello proc
и hello endp
определяется функция hello, которая ничего не делает.
Скомпилируем оба файла - app.c и hello.asm в одну программу. Рассмотрим напримере ассемблера MASM64 и компилятора для Visual C++ в Visual Studio. Для компиляции откроем программу x64 Native Tools Command Prompt for VS 2022, которая устанавливается вместе с Visual Studio, и перейдем в ней к папке, где располагаются файлы. Затем выполним следующюю команду
ml64 /c hello.asm
Опция /c
говорит, что файл hello.asm надо только скомпилировать в объектный файл. И после этой команды в папке программы будет скомпилирован файл
hello.obj
:
********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.5.5 ** Copyright (c) 2022 Microsoft Corporation ********************************************************************** [vcvarsall.bat] Environment initialized for: 'x64' C:\Program Files\Microsoft Visual Studio\2022\Community>cd c:\asm c:\asm>ml64 /c hello.asm Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm c:\asm>
Далее используем скомпилируем всю программу с помощью команды:
cl app.c hello.obj
В итоге будет скомпилирован файл app.exe, который мы можем запустить и который при запуске выполнит ассемблерную функцию hello:
c:\asm>cl app.c hello.obj Microsoft (R) C/C++ Optimizing Compiler Version 19.35.32217.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. app.c Microsoft (R) Incremental Linker Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:app.exe app.obj hello.obj c:\asm>app Hello starts Hello ends c:\asm>
Программа на C может вызывать код из файла на ассемблере, так и код на ассемблере может вызывать функции языка С. Например, используем стандартную функцию printf()
для вывода строки на консоль в программе на ассемблере. Для этого определим в файле hello.asm следующий код:
option casemap:none .data text byte "Hello, world!", 10, 0 ; определяем выводимые данные .code ; подключаем определение функции printf() из C/C++ externdef printf:proc ; определяем процедуру hello public hello hello proc sub rsp, 40 ; резервируем в стеке 40 байт lea rcx, text ; в регистр rcx загружаем адрес строки text call printf ; вызываем функцию printf add rsp, 40 ; восстанавливаем значение в стеке ret ; возвращаемся в вызывающий код hello endp end
Здесь для хранения данных определяем секцию .data
.data text byte "Hello, world!", 10, 0 ; определяем выводимые данные
Здесь определена строка text, которая представляет набор байт. Причем предпоследний байт - представляет число 10 - это числовой код перевода строки. Последний байт - 0, так как строки в Си должны завершаться нулевым байтом.
Для использования функции printf
подключаем ее с помощью директивы.
externdef printf:proc
Директива externdef имеет следующую форму
externdef symbol:type
Где symbol
— это внешный идентификатор, который мы хотим определить - в данном случае функция printf
.
А type
— тип этого идентификатора (для функций это тип proc
).
Стоит отметить, что директива externdef
должна применяться в секции .code
Функция printf() принимает как минимум один параметр - выводимую строку. Эта строка передается в функцию через регистр RCX. Для загрузки адреса строки в регистр rcx применяем инструкцию lea и затем вызываем функцию printf.
lea rcx, text call printf
Снова сначала скомпилируем код ассемблера из файла hello.asm с помощью команды:
ml64 /c hello.asm
И скомпилируем файл app.c в файл приложения с помощью команды:
cl app.c hello.obj
После компиляции запустим файл app.exe:
c:\asm>ml64 /c hello.asm Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm c:\asm>cl app.c hello.obj Microsoft (R) C/C++ Optimizing Compiler Version 19.35.32217.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. app.c Microsoft (R) Incremental Linker Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:app.exe app.obj hello.obj c:\asm>app Hello starts Hello, world! Hello ends c:\asm>