Взаимодействие с кодом на C/C++

Первая программа на MASM/C

Последнее обновление: 03.07.2023

Ассемблер 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>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850