Для создания файла в WinAPI можно использовать функцию CreateFileA. Она имеет следующий прототип на C++:
HANDLE CreateFileA( [in] LPCSTR lpFileName, [in] DWORD dwDesiredAccess, [in] DWORD dwShareMode, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, [in] DWORD dwCreationDisposition, [in] DWORD dwFlagsAndAttributes, [in, optional] HANDLE hTemplateFile );
Она принимает следующие параметры:
lpFileName
: строка - имя файла для открытия, передается через регистр RCX
dwDesiredAccess
: представляет 32-битное число и задает режим доступа (открытие на чтение, запись или на то и другое), передается через регистр RDX
Для создания и открытия файла на запись, устанавливаливается 30-й бит числа, а для чтения - 31-й бит. Примеры:
Открытие для записи: 040000000h
Открытие для чтения: 080000000h
Открытие для записи и чтения: 0C0000000h
Открытие для записи и дозаписи (если файл существует): 080000004h
dwShareMode
: режим общего доступа, передается через регистр R8
lpSecurityAttributes
: указатель на атрибуты безопасности, передается через регистр R9
dwCreationDisposition
: флаг, который указывает, надо ли создавать новый файл или открывать уже существующий. Передается через стек по адресу [rsp + 32]
Возможные значения:
1
: создавать новый, если только файл не существует, иначе открыть существующий
2
: всегда создавать заново, даже если файл с таким именем существует
3
: открыть существующий файл
4
: всегда открывать файл. Если он не существует, то он создается
dwFlagsAndAttributes
: атрибуты файла, передается через стек по адресу [rsp + 40]
. В частности, для создания обычного файла без атрибутов пережается число 128.
hTemplateFile
: шаблон дескриптора файла, передается через стек по адресу [rsp + 48]
После выполнения функция ReadFile
помещает в регистр RAX ненулевое значение, если запись прошла успешно, и ноль, если произошла ошибка.
Полное описание функции можно прочитать в документации.
К примеру создадим в текущей папке файл "test.txt":
includelib kernel32.lib ; подключаем библиотеку kernel32.lib ; подключаем функцию CreateFileA extrn CreateFileA: PROC .data filename byte "test.txt",0 ; имя файла .code main proc sub rsp, 56 lea rcx, filename ; имя файла mov rdx, 0C0000000h ; доступ на запись и чтение 1100 0000 0000 0000 0000 0000 0000 0000 xor r8, r8 ; Эксклюзивный доступ xor r9, r9 ; атрибуты безопасности отсутствуют mov r10, 2 ; Всегда создавать новый файл mov [rsp + 32], r10 mov r10, 128 ; обычный файл без атрибутов mov [rsp + 40], r10 mov [rsp + 48], r9 ; NULL - шаблон не используется call CreateFileA add rsp, 56 ret main endp end
В данном случае создаваемый файл будет называться "test.txt". Он будет доступен для чтения и для записи.
Для чтения надо установить 31-й бит 32-разрядного числа, а для записи - 30-й бит, поэтому в регистр RDX помещаем число 1100 0000 0000 0000 0000 0000 0000 0000
или
0C0000000h
в шестнадцатеричной системе.
Общий доступ к файлу нам не нужен, поэтому в регистр R8 помещаем число 0. Атрибуты безопасности в данном случае тоже не нужны, поэтому в регистр R9 также помещается число 0.
Чтобы файл создавался всегда, в стек по адресу [rsp + 32]
помещаем число 2.
Файл будет создаваться обычный без каких-то атрибутов, поэтому по адресу [rsp + 40]
в стек помещаем число 128.
Шаблон дескриптора файла тоже не важен, поэтому по адресу [rsp + 48]
в стек помещается число 0.
После вызова функции CreateFileA
будет создан файл "test.txt", а в регистр RAX при успешном открытии файла будет помещен дескриптор этого файла. В дальнейшем, используя данный дескриптор, мы можем
записывать или считывать файл. Если же в процессе открытия
произошла ошибка, то в RAX помещается число -1.
Если у нас уже есть файл, который мы хотим открыть для операций чтения-записи, то в стек по адресу [rsp + 32]
передается число 3.
Для закрытия файла применяется функция CloseHandle со следуюшим прототипом на языке C++:
BOOL CloseHandle( [in] HANDLE hObject );
В качестве параметра ей передается через регистр RCX дескриптор закрываемого файла. Результатом функции является ненулевое значение, если закрытие завершилось удачно, и 0, если произошла ошибка. Например:
includelib kernel32.lib ; подключаем библиотеку kernel32.lib extrn CreateFileA: proc extrn CloseHandle: proc .data filename byte "test.txt",0 ; имя файла .code ; Процедура открытия существующего файла ; Параметр - в RSI - имя файла ; Результат - в RAX - дескриптор открытого файла openFile proc sub rsp, 56 mov rcx, rsi ; имя файла mov rdx, 0C0000000h ; доступ на запись и чтение xor r8, r8 ; Эксклюзивный доступ xor r9, r9 ; атрибуты безопасности отсутствуют mov r10, 3 ; открыть существующий файл mov [rsp + 32], r10 mov r10, 128 ; обычный файл без атрибутов mov [rsp + 40], r10 mov [rsp + 48], r9 ; NULL - шаблон не используется call CreateFileA add rsp, 56 ret openFile endp ; Процедура закрытия существующего файла ; Параметр - в RAX - дескриптор открытого файла ; Результат - в RAX - состояние операции (ошибка или нет) closeFile proc sub rsp, 40 mov rcx, rax ; В RCX помещаем дескриптор файла call CloseHandle add rsp, 40 ret closeFile endp main proc lea rsi, filename call openFile ; открываем файл call closeFile ; закрываем файл ret main endp end
Здесь открытие файла вынесено в процедуру openFile, а закрытие - в closeFile. Перед вызовом closeFile предполагается, что в регистре RAX содержится дескриптор файла, полученный после вызова openFile.
В процессе работы с файлами могут возникать ошибки. Чтобы идентифицировать их WinAPI предоставляет функцию GetLastError:
DWORD GetLastError();
Данная функция не принимает параметров, лишь возвращает код ошибки. Применим эту функцию:
includelib kernel32.lib ; подключаем библиотеку kernel32.lib extrn CreateFileA: proc extrn CloseHandle: proc extrn GetLastError: proc .data filename byte "test.txt",0 ; имя файла .code ; Процедура открытия существующего файла ; Параметр - в RSI - имя файла ; Результат - в RAX - дескриптор открытого файла openFile proc sub rsp, 56 mov rcx, rsi ; имя файла mov rdx, 0C0000000h ; доступ на запись и чтение xor r8, r8 ; Эксклюзивный доступ xor r9, r9 ; атрибуты безопасности отсутствуют mov r10, 3 ; открыть существующий файл mov [rsp + 32], r10 mov r10, 128 ; обычный файл без атрибутов mov [rsp + 40], r10 mov [rsp + 48], r9 ; NULL - шаблон не используется call CreateFileA add rsp, 56 ret openFile endp ; Процедура закрытия существующего файла ; Параметр - в RAX - дескриптор открытого файла ; Результат - в RAX - состояние операции (ошибка или нет) closeFile proc sub rsp, 40 mov rcx, rax call CloseHandle add rsp, 40 ret closeFile endp ; Процедура получения последней ошибки Windows ; Результат - в RAX код ошибки getError proc sub rsp, 40 call GetLastError add rsp, 40 ret getError endp main proc lea rsi, filename call openFile ; открываем файл mov rax, 0 ; помещаем в RAX некорректный дескриптор файла call closeFile ; закрываем файл call getError ; получаем последнюю ошибку ret main endp end
Для имитации ошибки в процедуру closeFile передается некорректный дескриптор файла. Для получения ошибки после закрытия файла вызывается процедура getError, в которой получаем код ошибки.
После выполнения программы с помощью команды echo %ERRORLEVEL%
можно получить код ошибки, который хранится в RAX:
c:\asm>hello.exe c:\asm>echo %ERRORLEVEL% 6 c:\asm>
Здесь видно, что код ошибки - 6. Что это значит? Мы можем обратиться к документации, в частности, свериться по списку ошибок - https://learn.microsoft.com/ru-ru/windows/win32/debug/system-error-codes--0-499-. И мы увидим, что 6 - это код ошибки "ERROR_INVALID_HANDLE", то есть передан корректный дескриптор файла.