Программа на ассемблере ARM64 в Windows также может взаимодействовать с кодом С/С++, как и в Linux. Но есть ряд особенностей. Рассмотрим различные сценарии.
Для вызова в коде ассемблера стандартных библиотечных функций С/С++ в общем случае необходимо, чтобы код ассемблера запускался в программе на
С/С++. Это не всегда удобно, если мы хотим написать программу целиком на ассемблере, но при этом задействовать некоторые стандартные функции C/C+, например,
printf
. В этом случае надо дополнительно вызвать в коде ассемблера функцию _CRT_INIT. Например, определим в
файле "hello.asm" следующую программу на ассемблере:
global __main extern printf ; импортируем функцию printf extern _CRT_INIT area main, CODE __main str lr, [sp, #-16]! ; Сохраняем регистр LR в стек bl _CRT_INIT ; инициализация CRT ldr x0, =message ; отображаемое сообщение bl printf ; вызываем функцию printf ldr lr, [sp], #16 ; Восстанавливаем регистр LR из стека ret area DATA message dcb "Hello METANIT.COM!",0 ; сообщение в окне end
Здесь обращаемся к стандартной функции printf()
для вывода строки на консоль. Через регистр Х0 эта функция получает первый и единственный
аргумент - собственно строку для вывода. Но перед вызовом этой функции также вызывается функция _CRT_INIT
При компоновки необходимо передать компоновщику библиотеки "legacy_stdio_definitions.lib" и "msvcrt.lib". Процесс компиляции, компоновки и выполнения программы:
c:\arm64>armasm64 hello.asm -o hello.obj Microsoft (R) ARM Macro Assembler Version 14.36.32537.0 for 64 bits Copyright (C) Microsoft Corporation. All rights reserved. c:\arm64>link hello.obj legacy_stdio_definitions.lib msvcrt.lib /entry:__main /subsystem:console Microsoft (R) Incremental Linker Version 14.36.32537.0 Copyright (C) Microsoft Corporation. All rights reserved. c:\arm64>hello Hello METANIT.COM! c:\arm64>
Подобным образом мы можем вызывать в коде ассемблера и свои функции С/С++. Например, пусть у нас есть файл sum.c, в котором определена функция sum:
#include <stdio.h> int sum(int a, int b) { printf("a=%d\n", a); printf("b=%d\n", b); int result = a + b; printf("result=%d\n", result); return result; }
Это примитивная функция сложения, которая принимает два числа и возвращает их сумму, попутно логируя параметры и результат.
Пусть код на ассемблере в файле hello.asm вызывает эту функцию:
global __main extern _CRT_INIT extern sum ; импортируем функцию sum area main, CODE __main str lr, [sp, #-16]! ; Сохраняем регистр LR в стек bl _CRT_INIT mov x0, #10 ; a = 10 mov x1, #12 ; b = 12 bl sum ; вызываем функцию sum ldr lr, [sp], #16 ; Восстанавливаем регистр LR из стека ret end
Здесь через регистры Х0 и Х1 передаем функции sum соответственно значения для первого и второго параметров, затем вызываем функцию. Причем опять же
перед этим вызываем функцию _CRT_INIT
. Для компиляции кода на С используем программу компилятора cl. Процесс компиляции, компоновки и вызова программы будет следующим:
c:\arm64>armasm64 hello.asm -o hello.obj Microsoft (R) ARM Macro Assembler Version 14.36.32537.0 for 64 bits Copyright (C) Microsoft Corporation. All rights reserved. c:\arm64>cl hello.obj sum.c /link /entry:__main /subsystem:console legacy_stdio_definitions.lib msvcrt.lib /NODEFAULTLIB:libcmt.lib Microsoft (R) C/C++ Optimizing Compiler Version 19.36.32537 for ARM64 Copyright (C) Microsoft Corporation. All rights reserved. sum.c Microsoft (R) Incremental Linker Version 14.36.32537.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:hello.exe /entry:__main /subsystem:console legacy_stdio_definitions.lib msvcrt.lib /NODEFAULTLIB:libcmt.lib hello.obj sum.obj c:\arm64>hello a=10 b=12 result=22 c:\arm64>echo %ERRORLEVEL% 22 c:\arm64>
Здесь мы видим, что команда echo %ERRORLEVEL%
, которая возвращает по сути значение из регистра Х0, возвратила число 22 - результ выполнения функции sum.
Как и следовало ожидать.
Выше был представлен подход, когда основная программа написана на ассемблере. Однако обычно применяется другой подход - когда функция на C/C++ вызывает код на ассемблере. Хотя код ассемблере при этом также может обращаться к функциям С/С++.
Например, определим следующий файл hello.asm со следующим кодом на ассемблере:
global _hello extern printf ; импортируем функцию printf area hello, CODE _hello str lr, [sp, #-16]! ; Сохраняем регистр LR в стек ldr x0, =message ; устанавливаем параметр для функции printf bl printf ; вызываем функцию printf ldr lr, [sp], #16 ; Восстанавливаем регистр LR из стека ret area DATA message dcb "Hello METANIT.COM!\n",0 ; сообщение в окне end
Здесь определена функция _hello
, которая импортирует и вызывает стандартную функцию printf для вывода сообщения на экран. Никаких импортов и
вызово функции _CRT_INIT
здесь уже нет. Важно, что эта функция определена с директивой global:
global _hello
Несмотря на то, что это больше не точка входа в программу.
Далее определим файл app.c со следующим кодом на языке C:
#include <stdio.h> // подключаем внешнюю функцию hello из файла hello.asm extern void _hello(void); int main() { printf("_hello starts\n"); _hello(); printf("_hello ends\n"); }
Здесь подключаем ранее определенную в коде ассемблера функцию _hello и вызываем ее в функции main. Точкой входа в программу теперь будет данная функция main.
Сначала скомпилируем из файла hello.asm объектный файл hello.obj
armasm64 hello.asm -o hello.obj
Затем с помощью программы компилятора cl скомпилируем всю программу:
cl app.c hello.obj
Полный консольный вывод с компиляцией и выполнением программы:
c:\arm64>armasm64 hello.asm -o hello.obj Microsoft (R) ARM Macro Assembler Version 14.36.32537.0 for 64 bits Copyright (C) Microsoft Corporation. All rights reserved. c:\arm64>cl app.c hello.obj Microsoft (R) C/C++ Optimizing Compiler Version 19.36.32537 for ARM64 Copyright (C) Microsoft Corporation. All rights reserved. app.c Microsoft (R) Incremental Linker Version 14.36.32537.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:app.exe app.obj hello.obj c:\arm64>app _hello starts Hello METANIT.COM! _hello ends c:\arm64>