Для обращения к некоторым возможностям операционной системы Windows предоставляет некоторый функционал в виде функций WinAPI, которые можно вызывать в том числе и из программы на GNU ассемблере GAS. Для работы с WinAPI требуется подключить в программу одну и из библиотек, которые содержать необходимые функции.
Так, за работу с файлами отвечает библиотека kernel32. Рассмотрим на примере записи в файл, а точнее на его частном случае - выводе на консоль. Для записи в файл применяется функция WriteFile, которая имеет следующее определение на языке C++:
BOOL WriteFile( [in] HANDLE hFile, [in] LPCVOID lpBuffer, [in] DWORD nNumberOfBytesToWrite, [out, optional] LPDWORD lpNumberOfBytesWritten, [in, out, optional] LPOVERLAPPED lpOverlapped );
WriteFile
имеет следующие параметры:
hFile
: дескриптор файла, передается через регистр X0
lpBuffer
: адрес буфера для записи, передается через регистр X1
nNumberOfBytesToWrite
: размер буфера, передается через регистр Х2
lpNumberOfBytesWritten
: адрес переменной типа dword для получения количества байтов, записанных в файл. Равно размеру буфера, если операция записи прошла успешно.
Передается через регистр Х3
lpOverlapped
: обычно имеет значение NULL (0), передается через регистр Х4
После выполнения функция WriteFile
возвращает ненулевое значение, если запись прошла успешно, и ноль, если произошла ошибка.
Но чтобы записать данные в файл, нам необходим дескриптор этого файла. В зависимости от того, куда мы хотим записать, отличается способ получения дескриптора. В данном же случае мы будем выводить данные на консоль, и для этого нам потребуется встроенная функция GetStdHandle
HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle );
Она принимает один параметр - число, которое указывает, для какого устройства надо получить дескриптор. Этому параметру можно передать следующие значения:
-10: устройство стандартного ввода (для ввода с консоли)
-11: устройство стандартного вывода (для вывода на консоль)
-12: для вывода ошибок на консоль
В качестве результата функция возвращает дескриптор консоли.
Третья функция, которую мы далее используем - функция выхода из программы - ExitProcess:
void ExitProcess( [in] UINT uExitCode );
Она принимает один параметр - статусный код возврата.
Используем функцию WriteFile
для записи (вывода) данных на консоль, а точнее в стандартный поток вывода:
.globl _start .data msg: .asciz "Hello METANIT.COM!\n" .equ msg_len, .-msg .text _start: subq $48, %rsp movq $-11, %rcx # -11 - значение для стандартного вывода call GetStdHandle # получаем дескриптор стандартного вывода movq %rax, %rcx # первый параметр - дескриптор файла leaq msg(%rip), %rdx # второй параметр - строка movq $msg_len, %r8 # третий параметр - длина строки movq $0, %r9 # четвертый параметр - выходной параметр, нам не интересен movq $0, 32(%rsp) # для 5-го параметра call WriteFile # вызываем функцию записи в файла - вывода на консоль movq $22, %rcx # код статуса call ExitProcess # выходим из программы addq $48, %rsp # восстанавливаем стек
Передача параметров в функции и возвращение результатов применяет те же соглашения, что действуют в целом в Windows: первые 4 целочисленных параметра передаются последовательно через регистры RCX, RDX, R8, R9. А все последующие параметры передаются через стек. Результат функции помещается в регистр RAX.
Здесь функции GetStdHandle
в качестве параметра через регистр RCX передается значение -11, которое указывает, что надо получить
дескриптор стандартного вывода.
Вызов функций WinAPI выполняется также как и вызов любых других функций - с помощью инструкции call
movq $-11, %rcx # -11 - значение для стандартного вывода call GetStdHandle # получаем дескриптор стандартного вывода
Полученный через регистр RAX дескриптор через регистр RCX будет передаваться первому параметру функции WriteFile. Через регистр RDX передается адрес выводимой строки - переменной message, а через регистр R8 - длина строки - константа len. Остальные - 4 и 5 параметры нам не важны. Поэтому для четвертого параметра через регистр R9 передаем 0, а для пятого параметра через стек также передаем 0.
movq %rax, %rcx # первый параметр - дескриптор файла leaq msg(%rip), %rdx # второй параметр - строка movq $msg_len, %r8 # третий параметр - длина строки movq $0, %r9 # четвертный параметр - выходной параметр, сколько байтов реально записано movq $0, 32(%rsp) # для 5-го параметра call WriteFile # вызываем функцию записи в файла - вывода на консоль
В конце программы вызываем функцию ExitProcess для выхода из программы, передавая в функцию через регистр RCX статусный код возврата:
movq $22, %rcx # код статуса call ExitProcess # выходим из программы
Допустим, исходный код программы определен в файле hello.s, то компиляция будет выглядеть стандартным образом:
as hello.s -o hello.o
Поскольку используемые функции располагаются в библиотеке kernel32, то эту библиотеку необходимо передать компоновщику при компоновке программы. То есть команда компоновки будет выгдядеть следующим образом
ld -o hello.exe hello.o -lkernel32
После создания исполняемого файла запустим программу, и нам отобразится строка. В итоге весь процесс компиляции/компоновки и вызов программы будет выглядеть следующим образом :
c:\asm>as hello.s -o hello.o c:\asm>ld -o hello.exe hello.o -lkernel32 c:\asm>hello.exe Hello METANIT.COM! c:\asm>echo %ERRORLEVEL% 22 c:\asm>