Взаимодействие с WinAPI

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

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