Перехват ввода и вывода консольных программ бывает нужен, когда требуется получить результат их работы для обработки в нашем приложении. Также мы получаем возможность передавать консольным программам собственные данные. Как обычно в FASM'е готовых решений нет, пришлось разбираться самому и портировать с языков высокого уровня. Технически перехват ввода и вывода консоли выполняется с использованием специальных структур, называемых "
Pipe". По принципу действия они и вправду похожи на трубы: в один конец информация "вливается", из другого "выливается", а перехват является просто подключением нашего "крана" к тому или иному концу трубы. Для перехвата требуется переопределить стандартные дескрипторы ввода и вывода консольного приложения на наши. Создать новые дескрипторы можно при помощи функции
CreatePipe, а затем прописать в структуру STARTUPINFO запускаемого приложения. После этого новые дескрипторы будут доступны для чтения и записи как обычный файл.
В сегменте данных родительского приложения требуется определить следующие переменные и структуры:
; Сегмент данных
section '.data' data readable writeable
; Данные для перехвата консоли
newstdin dd ? ; Новый дескриптор стандартного ввода
newstdout dd ? ; Новый дескриптор стандартного вывода
read_stdout dd ? ; Дескриптор для использования ReadFile
write_stdin dd ? ; Дескриптор для использования WriteFile
bytestoread dd ? ; Всего байт в буфере консоли
available dd ? ; Счетчик байт, доступных для чтения из консоли
; Эта структура по умолчанию не определена, сделаем это сами
struct SECURITY_ATTRIBUTES
nLength dd ?
lpSecurityDescriptor dd ?
bInheritHandle dd ?
ends
; Описание структур для запуска консольной программы и настройки дескрипторов
sinfo STARTUPINFO
sattr SECURITY_ATTRIBUTES
pinfo PROCESS_INFORMATION
; Дополнительно зарезервируем буфер для чтения информации
buff rb 1024
Буфер большого размера для чтения данных лучше не использовать, вполне достаточно 1 килобайта. Количество байт, доступных для чтения из консоли, можно получить при помощи функции
PeekNamedPipe. Обратите внимание, что фактически данные из консоли при этом не забираются, это надо будет сделать при помощи функции чтения файла
ReadFile. Вот пример кода перехватчика вывода консоли.
; Сегмент кода
section '.code' code readable executable
; Заполнить структуру SECURITY_ATTRIBUTES
mov [sattr.nLength],sizeof.SECURITY_ATTRIBUTES
mov [sattr.lpSecurityDescriptor],NULL
mov [sattr.bInheritHandle],TRUE
; Создать новые дескрипторы для консольного ввода и вывода
invoke CreatePipe,newstdin,write_stdin,sattr,NULL
invoke CreatePipe,read_stdout,newstdout,sattr,NULL
; Заполнить структуру STARTUPINFO данными процесса
invoke GetStartupInfo,sinfo
; Установить флаги: использовать собственные дескрипторы
; ввода-вывода и возможность изменять видимость окна процесса
mov [sinfo.dwFlags],STARTF_USESTDHANDLES+STARTF_USESHOWWINDOW
mov [sinfo.wShowWindow],SW_HIDE
mov eax,[newstdout]
; Установить наш дескриптор для стандартного вывода и ошибок
mov [sinfo.hStdOutput],eax
mov [sinfo.hStdError],eax
mov eax,[newstdin]
; Установить наш дескриптор для стандартного ввода
mov [sinfo.hStdInput],eax
; Запустить консольное приложение в режиме suspended
invoke CreateProcess, NULL, fname, NULL, NULL, TRUE,\
CREATE_SUSPENDED+NORMAL_PRIORITY_CLASS+CREATE_NEW_CONSOLE,\
NULL,NULL,sinfo,pinfo
; Тут можно выполнить какие-то промежуточные действия, например
; спросить у пользователя куда сохранять файл, или проверить
; доступность каких-либо данных
; Отпустить замороженный процесс на выполнение
invoke ResumeThread,[pinfo.hThread]
wait_for_finish:
; Проверить активен ли еще запущенный процесс
invoke GetExitCodeProcess,[pinfo.hProcess],tmp
cmp [tmp],STILL_ACTIVE
jne finish
; Небольшая пауза чтобы не грузить систему
invoke Sleep,10
; Прочитать данные из буфера консоли без удаления
invoke PeekNamedPipe,[read_stdout],buff,1023,bytestoread,available,NULL
; Если консоль еще ничего не вывела, то продолжать ждать результат
cmp [bytestoread],0
je wait_for_finish
read_data_from_pipe:
; Прочитать данные из буфера консоли и записать в наш файл
invoke ReadFile,[read_stdout],buff,1023,bytestoread,NULL
; Теперь в буфере buff содержатся данные из консоли. Их можно
; проанализировать, вывести в форму оконного приложения, сохранить
; в файл или просто проигнорировать
; Дополнительная проверка если весь вывод был меньше размера буфера
cmp [available],1023
jna wait_for_finish
; Продолжать чтение до окончания данных
cmp [bytestoread],1023
je read_data_from_pipe
jmp wait_for_finish
finish:
; Завершить обработку
Если не требуется обработка данных из консольного приложения, то можно сразу перенаправить его, например, на дескриптор открытого файла или принтер. Для передачи данных в поток консольного приложения надо отправлять их через функцию
WriteFile на дескриптор, описанный в этом примере как write_stdin.
В аттаче рабочий пример программы, перехватывающей консольный вывод и сохраняющей его в файл console.txt. В примере выполняется команда "cmd /c dir c:\", выводящая список файлов и каталогов в корне диска C: