Есть чужая dll выводящая текст на консоль. Цепляется к оконному приложению. Подскажите, будте любезны, как перехватить консольный вывод. Пытался использовать треды. В один прописал - SetStdHandle и поставил pipe. В другом бесконечный цикл с PeekNamedPipe ну и затем ReadFile. Вызываю функцию dll. Она кидает текст - и текст ИНОГДА (редко) отлавливается. Может можно проще как нибудь ?
Я поковырял этот вопрос, вот что получилось: 1. В GUI-приложении пишем процедуру, которая будет принимать во втором параметре строку (как WriteConsole). 2. В Иде узнаем адрес этой процедуры (напр. 40111Eh). 3. В Dll-ке находим вызов WriteConsole (что-то типа call [????????]) и вставляем вместо него код команд mov eax, 40111Eh / call eax. В результате вызывается наша процедура вместо WriteConsole со строкой во втором параметре. К сожалению, call [????????] занимает 6 байт, а mov eax, 40111Eh / call eax - 7, поэтому процедура перед возвратом в DLL должна сама выполнить затертую команду. У меня она выглядит так: Код (Text): QuasiWriteConsole proc near mov eax,[esp + 8] ;адрес строки push 0 push offset the_title push eax push 0 call MessageBox pop eax ;EAX: адрес возврата add esp,5 * 4 ;очистить стек от параметров pop edi ;эта команда была у меня в DLL-ке после WriteConsole ;(а может быть и не однобайтная!) jmp eax ;возврат QuasiWriteConsole endp Результат в аттаче. _969651039__GuiApp.rar PS. А проще и лучше править адрес WriteConsole в таблице импорта у DLL-ки
vinnie_pooh Мысль править DDL-ку мне понравилась, но применить оказалось не судьба. гадкая dll состряпана в Delphi и WriteConsole не использует в принципе (смотрел и таблицу импорта и дебагом пробегал). То есть таблицу импорта править не удасться. Хуже того, в pascal свой Write при вызове которого генерируется нечто подобное - Код (Text): MyTest.pas.32: Writeln('Init Test'); 00B158C8 A13464B100 mov eax,[$00b16434] 00B158CD BAEC58B100 mov edx,$00b158ec 00B158D2 E8CDEEFEFF call @Write0LString 00B158D7 E868D8FEFF call @WriteLn 00B158DC E8F7D1FEFF call @_IOTest то есть целых три call. При том в каждом конкретном случае генерируется различный код (различные типы параметров, проверка ошибки ввода-вывода и т.п.) То есть и первый вариант найти все вызовы и поменять на нужное оказывается слишком хлопотным. Вызовов до чертиков а искать их придется практически в ручную (во всяком случае я не знаю - какой шаблон поиска задать) Так что вопрос остается открытым
Но, по идее то, эти ф-ции должны сводиться к WriteConsole или WriteFile, попробуй бряк на них, чтобы узнать то место, которое нужно править
Ежели "гадкая dll состряпана в Delphi", то возможно и править ее не надо, т.к. в дельфях есть легальный способ перенаправления вывода. Вывод на консоль идет как и в любой текстовый файл, но вместо произвольной файловой переменной f используется глобальная переменная Output, инициализируемая при загрузке модуля System. Все что нужно для write\writeln содержится в самой переменной Output, поэтому достаточно до нее добраться и можно перенаправить вывод куда угодно, заменив соответствующие поля TTextRec(Output). Например, можно заменить TTextRec(Output).Handle на pipe. Но лучше задать фиктивный валидный хэндл и подменить указатель на функцию вывода строки TTextRec(Output).InOutFunc. Тип TTextRec описан в модуле SysUtils: Код (Text): TTextRec = record Handle: Integer; //хэндл Mode: Integer; //режим вывода = fmOutput = $D7B2 BufSize: Cardinal; //размер буфера строки BufPos: Cardinal; //для записи - число символов в буфере BufEnd: Cardinal; //для чтения - число прочитанных байт BufPtr: PChar; //указатель на буфер строки OpenFunc: Pointer; //функция открытия файла InOutFunc: Pointer; //функция ввода-вывода строки FlushFunc: Pointer; //функция выталкивания остатка буфера CloseFunc: Pointer; //функция закрытия файла UserData: array[1..32] of Byte; //не используется Name: array[0..259] of Char; //имя файла; для консоли = '' Buffer: TTextBuf; //=array[0..127] of Char; буфер строки по умолчанию end; const fmClosed = $D7B0; fmInput = $D7B1; fmOutput = $D7B2; Все функции имеют один прототип: function DeviceFunc(var F: TTextRec):integer == DeviceFunc(FP:pointer):integer, где FP - указатель на TTextRec; тип вызова register, т.е. на входе в EAX сидит указатель на F. Если OK, то Result = 0, иначе это код ошибки IOResult. Для перехвата записи строки нужно в своем приложении изменить поля переменной Output dll: 1) на всякий случай проверить и закрыть Handle (хотя при инициализации он = 0 и открывается только при первой записи) 2) задать валидное значение Handle (<> 0 и INVALID_HANDLE) 3) установить Mode = fmOutput 4) определить функцию InOutFunc, которая будет копировать BufPos символов из буфера BufPtr и занулять BufPos (вместо BufPtr можно использовать Buffer, т.к. для консоли BufPtr = указателю на Buffer) 5) остальные функции заменить на пустышку, которая просто возвращвет 0 в eax. Как найти указатель на Output ? Он передается в EAX во все writeXX, т.е. в приведенном примере это mov eax,[$00b16434]. Косвенная ссылка - это дельфийские прибамбасы, наверняка сама структура Output сидит в .BSS, а ссылка на нее в .DATA
leo Спасибо за совет, так я и сделал. Единственно что пришлось добавить - указать размер буфера (BufSize) равным 1. Без этого вызов функции InOutFunc произходил только при закрытии программы. А так, насколько я понял, буфер при выводе каждого символа заполняется и принудительно вызывает фунцию сброса данных.
yunus Да, ты прав. Покопался я в сорцах (..\rtl\sys\assign.asm и writestr.asm) и пришел к выводу, что при обычном write символы просто добавляются в буфер, а запись в файл производится в следующих случаях: 1) при переполнении буфера: BufSize-BufPtr < числа добавляемых символов 2) при закрытии файла close(F); в данном сл. Output закрывается при завершении программы 3) при writeln вызывается FlushFunc Поэтому можно попробовать установить FlushFunc = InOutFunc, а BufSize задать порядка 4-8 (на случай write, т.к. если вывод идет по writeln, то и 128 можно оставить). А то копировать по одному символу ИМХО как-то "не изящно" (но с другой стороны - надежно).