Для чтения данных мы можем использовать функцию ReadFile, которая рассматривалась на примерах консольного ввода. Только в данном случае ввод будет идти из файла,
для чего функции ReadFile передается дескриптор открытого файла. Допустим, нам надо считать файл test.txt
, который имеет следующее содержимое:
Hello METANIT.COM1 Hello METANIT.COM2 Hello METANIT.COM3 Hello METANIT.COM4 Hello METANIT.COM5
Для считавания этого файла и вывода считанных данных на консоль определим следующую программу:
includelib kernel32.lib extrn GetStdHandle: proc extrn ReadFile: proc extrn WriteFile: proc extrn CreateFileA: proc extrn CloseHandle: proc .data buffer byte 32 dup (?) ; буфер для чтения bufferLen = $ - buffer ; длина буфера filename byte "test.txt" ; имя файла fHandle qword 0 ; дескриптор файла stdout qword 0 ; дескриптор консольного вывода stdoutSet byte 0 ; установлен ли дескриптор консольного вывода .code ; Параметры: ; RSI - адрес строки ; RCX - длина строки ; RAX - дескриптор файла ; Результат - через RAX возвращаем количество записанных байтов write proc sub rsp, 56 mov rdx, rsi ; Второй параметр - строка mov r8, rcx ; Третий параметр - длина строки mov rcx, rax ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консольного вывода lea r9, bytesWritten ; Четвертый параметр WriteFile - адрес для получения записанных байтов mov qword ptr [rsp + 32], 0 ; Пятый параметр WriteFile call WriteFile test rax, rax ; проверяем на наличие ошибки mov eax, bytesWritten ; если все нормально, помещаем в RAX количество записанных байтов jnz exit mov rax, -1 ; Возвращаем через RAX код ошибки exit: add rsp, 56 ret bytesWritten equ [rsp+40] write endp ; Процедура вывода произвольной строки на консоль ; Параметры ; RSI - адрес строки ; RCX - количество символов ; Результат - в RAX количество записанных байтов или -1, если произошла ошибка writeToConsole proc cmp stdoutSet, 1 jz writeData ; если дескриптор консольного вывода установлен sub rsp, 32 push rcx ; сохраняем количество символов mov rcx, -11 ; Аргумент для GetStdHandle - STD_OUTPUT call GetStdHandle ; вызываем функцию GetStdHandle pop rcx ; восстанавливаем RCX - количество символов add rsp, 32 mov stdout, rax ; помещаем в stdout дескриптор консольного вывода mov stdoutSet, 1 ; дескриптор консольного вывода установлен writeData: mov rax, stdout call write ret writeToConsole endp ; Процедура для считывания данных ; Параметры: ; RAX - дескриптор файла, ; RDI - буфер для считывания ; RCX - длина буфера ; Результат - в RAX количетсво записанных байтов или -1, если произошла ошибка read proc sub rsp, 56 mov r8, rcx ;Третий параметр ReadFile - размер буфера mov rcx, rax ; Первый параметр ReadFile - дескриптор файла mov rdx, rdi ; Второй параметр ReadFile - адрес буфера lea r9, bytesRead ; Четвертый параметр ReadFile - количество считанных байтов mov qword ptr [rsp + 32], 0 ; Пятый параметр ReadFile - 0 call ReadFile ; вызов функции ReadFile test rax, rax ; проверяем на ошибку mov eax, bytesRead ; если ошибки нет, в RAX количество считанных байтов jnz exit ; если в RAX ненулевое значение mov rax, -1 ; помещаем -1 exit: add rsp, 56 ret bytesRead equ [rsp+40] ; область в стеке для хранения количества считанных байтов read endp ; Процедура открытия существующего файла ; Параметр - в RSI - имя файла ; Результат - в RAX - дескриптор открытого файла openFile proc sub rsp, 56 mov rcx, rsi ; имя файла mov rdx, 0C0000000h ; доступ на чтение xor r8, r8 ; Эксклюзивный доступ xor r9, r9 ; атрибуты безопасности отсутствуют mov r10, 4 ; открыть существующий файл, если он не существует - создать 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 ; открываем файл mov fHandle, rax ; сохраняем дескриптор файла mov rax, fHandle ; в RAX дескриптор файла lea rdi, buffer mov rcx, bufferLen call read ; считываем за раз bufferLen байт в массив buffer mov rcx, rax ; далее передаем считанное количество байт для вывода на консоль lea rsi, buffer call writeToConsole ; выводим прочитанные данные на консоль mov rax, fHandle ; в RAX дескриптор файла call closeFile ; закрываем файл ret main endp end
В процедуре main вызываем процедуру openFile, которая в свою очередь использует функцию CreateFileA
для открытия файла и получения его дескриптора. Полученный дескриптор файла
сохраняется в переменную fHandle.
Далее считываем в процедуре read bufferLen байт в массив buffer. Процедура read получает через регистр RAX дескриптор файла и вызывает функцию ReadFile
для считывания данных.
После этого данные считаны в buffer, а в регистре RAX содержится количество считанных байт. Для вывода считанных символов на консоль вызываем процедуру writeToConsole, которая в свою очередь получает
дескриптор консольного вывода с помощью функции GetStdHandle
для собственно вывода данных вызывает процедуру write. Процедура write для вывода данных получает дескриптор консольного вывода и
вызывает функцию WriteFile
.
После завершения считывания вызываем процедуру closeFile для закрытия файла.
Но в данном случае мы сталкиваемся с проблемой. Если мы скомпилируем и запустим приложение, то увидим, что файл считывается не полностью:
c:\asm>hello Hello METANIT.COM1 Hello METANIT c:\asm>
Мы имеем ограниченный размер буфера (в примере выше - 32 байта), однако мы не знаем точный размер файла. Вполне возможно он будет гораздо больше размера буфера. В этом случае мы можем считывать часть файла в буфер в цикле, пока не считаем весь файл. Для этого изменим процедуру main следующим образом:
main proc lea rsi, filename call openFile ; открываем файл mov fHandle, rax ; сохраняем дескриптор файла ; считываем в цикле за раз bufferLen байт readLoop: mov rax, fHandle ; в RAX дескриптор файла lea rdi, buffer mov rcx, bufferLen call read ; считываем bufferLen байт в buffer test eax, eax ; проверяем количество считанных байт jz done ; если считано 0 байт, завершаем считывание mov rcx, rax ; иначе передаем количество байт для вывода на консоль lea rsi, buffer call writeToConsole ; выводим прочитанные данные на консоль jmp readLoop done: mov rax, fHandle ; в RAX дескриптор файла call closeFile ; закрываем файл ret main endp
Теперь считывание и вывод символов проецируется на метку readLoop. За раз считываем bufferLen байт (32 байта) и сохраняем в буфер и сразу выводим их на консоль. Если количестве считанных байт равно 0, значит, весь файл считан, поэтому выходим из цикла и переходим к закрытию файла. Таким образом, мы считаем весь файл. Пример работы приложения:
c:\asm>hello.exe Hello METANIT.COM1 Hello METANIT.COM2 Hello METANIT.COM3 Hello METANIT.COM4 Hello METANIT.COM5 c:\asm>