Writing drivers for KolibriOS/ru: Difference between revisions

From KolibriOS wiki
Jump to navigation Jump to search
(update manual. 1/5 part)
(Global update of the article, part 1)
Line 1: Line 1:
{{DISPLAYTITLE:Пишем драйвер для КолибриОС}}
{{DISPLAYTITLE:Пишем драйвер для КолибриОС}}
'''ПРЕДУПРЕЖДЕНИЕ: Данная статья устарела и нуждается в обновлении/переписывании. Сейчас ядро использует для драйверов формат PE вместо MS COFF.'''  
 
''' ВНИМАНИЕ: Данная статья находится в разработке и содержит множество устаревших и непроверенных данных. В данный момент использование этой статьи в качестве справочного материала не рекомендуется.  '''
 
'''ПРЕДУПРЕЖДЕНИЕ: Данная статья устарела и переписывается. НЕ ИСПОЛЬЗУЙТЕ ЭТУ СТАТЬЮ.'''  
 
== Вступление ==
== Вступление ==


Line 1,158: Line 1,162:
mem:
mem:
</syntaxhighlight>
</syntaxhighlight>
== Мьютексы и семафоры ==
== Работа с подсистемой событий ==
== Интерфейс работы с дисковой подсистемой ==
== Интерфейс работы со встроенными устройствами ==
=== Прерывания ===
=== Шина PCI ===
=== Шина USB ===
=== Порты ввода/вывода ===
== Интерфейс взаимодействия с сетевой подсистемой ==
== Функции взаимодействия с графической подсистемой ==
== Остальные импортируемые ядром функции ==
=== Функция для драйверов мыши ===
=== Функции для драйверов клавиатуры ===
=== Функции работы с потоками ===
=== Функции таймера ===
== Полезные макросы языка fasm и особенности их применения ==
Для языка fasm использование макросов достаточно сильно может упростить написание программ и драйверов.
Кроме пользы, макросы могут внести путаницу в поиске ошибок реализации любой программы.
=== DEBUGF ===


[[Category:Руководства]]
[[Category:Руководства]]

Revision as of 11:00, 22 October 2023


ВНИМАНИЕ: Данная статья находится в разработке и содержит множество устаревших и непроверенных данных. В данный момент использование этой статьи в качестве справочного материала не рекомендуется.

ПРЕДУПРЕЖДЕНИЕ: Данная статья устарела и переписывается. НЕ ИСПОЛЬЗУЙТЕ ЭТУ СТАТЬЮ.

Вступление

Предупреждение 1. Прежде чем писать драйвер, хорошо подумайте, нельзя ли обойтись средствами прикладных API, в частности, функций работы с оборудованием 46 и 62. Во-первых, от ошибки в кривом приложении пострадает только это кривое приложение, а кривой драйвер способен без особого труда обрушить всю систему. Во-вторых, для приложений можно вылавливать баги в отладчике MTDBG, обладающем определёнными возможностями, а для драйверов этот путь закрыт (разве что встроенный отладчик эмулятора Bochs, но он заведомо непригоден для отладки с реальным железом), так что единственным средством остаётся отладочный вывод на доску отладки BOARD со всеми недостатками.

Далее допустим, что вы всё ещё читаете эту статью. Мало ли, может, вы всегда пишете код с первого раза безошибочно (чего только на свете не бывает), или в совершенстве владеете отладкой прямо в мозгу и считаете всякие отладочные средства баловством, или просто считаете, что настоящий мужчина (настоящая леди?) не боится трудностей и несколькими строчками текста вас не напугать.

Предупреждение 2. Драйвера, естественно, тесно связаны с ядром. А в ядро КолибриОС вносятся изменения несколько раз в неделю. Разумеется, большинство изменений никак не касается драйверной подсистемы, но иногда добавляются/исчезают/изменяются важные системные функции, экспортируемые драйверам. Поэтому если вы возьмёте и скомпилируете прилагаемый к статье код, то, возможно, он прямо в таком виде работать не будет. Так что внимательно читайте текст - я постараюсь выделить по возможности все причины неработоспособности в будущем и требуемые модификации. Прилагаемый к статье код рассчитан на ревизию {{#svn_rev:450}}, последнюю на момент написания этих строк (в дистрибутиве 0.6.5.0 работать в таком виде не будет).

Вообще-то основная задача драйверов - обеспечить работу с оборудованием. Но поскольку эта статья ставит своей целью показать принципы работы драйверов, а для реализации основной задачи нужно много кода, работающего именно с железом и не имеющего никакого отношения к драйверной подсистеме, то процесс написания драйвера показан на следующем примере: создадим драйвер, перехватывающий и записывающий все обращения приложений к файловой системе, и управляющую программу, которая получает данные от драйвера и отображает их. В качестве средства разработки используется FASM. Архив к статье находится здесь.

Описание работы с драйверной подсистемой

Драйверная подсистема позволяет загружать драйвера в формате PE и работать с ними. Для загрузки драйверов в ядре придусмотрено 2 сисфункции 68.16 и 68.21 а для управления драйвером сисфункция 68.17. Драйвер должен иметь экспортируемую функцию START, которая должна возвращать хандлер стуктуры драйвера в случае успеха, и ноль в случае, если драйвер не загружен. Для работы с ядром драйверу нужно импортировать некоторые функции ядра, которые экспортируются ядром как core.dll.

Драйвер

Специально для желающих написать свой драйвер предоставляется каркас драйвера. Он находится в svn-репозитории вместе с ядром, точнее, в папке {{#svn:/kernel/trunk/drivers|svn://kolibrios.org/kernel/trunk/drivers}}. Ну что же, давайте посмотрим ({{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm из #450|450}}):

<syntaxhighlight lang="asm">

;;
Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;;
Distributed under terms of the GNU General Public License  ;;
;;
driver sceletone

format PE DLL native 0.05 entry START

section '.flat' code readable writable executable include 'proc32.inc' include 'struct.inc' include 'macros.inc' include 'peimport.inc'

</syntaxhighlight>

Ну, "Copyright" - он и есть копирайт, комментарий в следующей строке извещает нас, куда же мы собственно попали, это неинтересно. Дальше мы должны известить компилятор, какой формат мы хотим получить. Драйвера должны иметь формат PE. В общем, так должно быть для всех драйверов. Дальше идёт указание функции START как точки входа, которую вызовет ядро после загрузки драйвера. После этого объявляется секция ".flat", в которой будет располагаться код нашего драйвера, после объявления секции подключаем вспомогательные файлы. {{#svn:/drivers/proc32.inc|proc32.inc}} содержит макросы для определения и вызова стандартных процедур (proc/endp, stdcall/ccall/invoke/cinvoke, local для создания локальных переменных) и находится в той же папке, что и {{#svn:/drivers/sceletone.asm|sceletone.asm}}. Его можно включать или не включать, макросы оттуда можно использовать или не использовать, в этой статье они используются, чтобы не усложнять восприятие (вообще говоря, при стремлении к максимальной эффективности использование макросов может повредить, но это тема отдельных жарких споров). {{#svn:/drivers/peimport.inc|peimport.inc}} содержит объявления для всех экспортируемых функций ядра. Загляните туда, ничего сложного там нет, просто куча стереотипных конструкций. На самом деле (в смысле того, как разрешается импорт при загрузке драйвера) список всех экспортируемых функций и данных ядра находится в файле {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}} (метка kernel_export), так что если вам как программисту ядра вдруг понадобиться что-нибудь своё экспортировать, лезьте туда (ну и {{#svn:/drivers/peimport.inc|peimport.inc}} отредактируйте из вежливости к другим).

Внимание! Здесь появляется возможность несовместимости: если вы используете какие-нибудь функции, которых (ещё или уже) нет в ядре, на которое вы рассчитываете, ядро откажется грузить ваш драйвер (ругнувшись на доске отладки нехорошим словом на ненашем языке "unresolved" с указанием имени функции).

Ну что же, идём дальше:

<syntaxhighlight lang="asm"> DEBUG equ 1 ; for debug output in board

API_VERSION equ 0 ;version api this driver

STRIDE equ 4 ;size of row in devices table

SRV_GETVERSION equ 0 ; number function for get api version

</syntaxhighlight>

Это просто объявление констант

<syntaxhighlight lang="asm">

proc START c, state:dword, cmdline:dword

       cmp     [state], 1
       jne     .exit

.entry:

       push    esi
    if DEBUG
       mov     esi, msgInit
       invoke  SysMsgBoardStr
    end if
       call    detect
       pop     esi
       test    eax, eax
       jz      .fail
       invoke  RegService, my_service, service_proc
       ret

.fail: .exit:

       xor     eax, eax
       ret

endp

</syntaxhighlight>

Выше мы создали функцию START, о которой уже говорилось ранее. Данная функция принимает 2 аргумента: state и cmdline и возвращает хэндлер драйвера, полученный при вызове RegService. Если эта функция вернёт 0, то это будет означать, что драйвер по какой-то причине не может работать(либо нет нужного оборудования, с которым бы драйвер работал, либо его что-то не устраивает и чтобы не создавать ошибок, драйвер завершает работу). Параметр cmdline это командная строка в ascii кодировке, state это действие, которое требует от этой функции ядро. Сейчас есть 2 значения этого аргумента: DRV_ENTRY передаваемое при старте драйвера и DRV_EXIT передаваемое при завершении работы драйвера. Эти значения определены в файле {{#svn:/drivers/macros.inc|macros.inc}}. В этом коде вызываются 2 функции импортированные из ядра: SysMsgBoardStr и RegService, функции ядра имеют разные соглашения о вызовах, в одних параметры передаются через стек, в других через регистры. Описание соглашения о вызовах функций можно найти в файле {{#svn:/kernel/trunk/core/exports.inc|exports.inc}} в комментарии возле экспортируемой функции.

Процедура START - это процедура, которая вызывается системой при загрузке драйвера и при завершении работы. В первом случае она должна инициализировать драйвер, во втором - наоборот. О ней речь пойдёт чуть позже.

Процедуру service_proc экспортировать совершенно ненужно, о её размещении ядро узнаёт по другим каналам, о ней речь пойдёт ещё позже.

Последняя порция констант

<syntaxhighlight lang="asm"> DEBUG equ 1

STRIDE equ 4 ;size of row in devices table </syntaxhighlight>

(из которых первая включает код отладочного вывода в блоках if DEBUG/end if, последняя нужна для красоты и ни для чего больше) и мы наконец-то переходим к изучению кода:

<syntaxhighlight lang="asm"> section '.flat' code readable align 16 </syntaxhighlight>

означает ровно-таки то, что написано;

<syntaxhighlight lang="asm"> proc START stdcall, state:dword

          cmp [state], 1
          jne .exit

.entry:

    if DEBUG
          mov esi, msgInit
          call SysMsgBoardStr
    end if
          stdcall RegService, my_service, service_proc

ret .fail: .exit:

          xor eax, eax
          ret

endp </syntaxhighlight>

Это код процедуры инициализации/финализации. При загрузке драйвера она вызывается с аргументом DRV_ENTRY = 1 и должна вернуть ненулевое значение при успехе. При завершении системы она вызывается с аргументом DRV_EXIT = -1. В нашем случае драйвер не работает ни с каким железом, так что ни инициализации никакого железа, ни вообще никакой финализации нет, а есть только минимально необходимые действия, чтобы драйвер считался загруженным, а именно, регистрация. Функция RegService экспортируется ядром и принимает два аргумента: имя драйвера (до 16 символов, включая завершающий 0) и указатель на процедуру обработки I/O, а возвращает 0 при неудаче или (ненулевой) зарегистрированный хэндл при успехе. Кстати, как узнать, что делает та или иная экспортируемая функция? Допустим, нам позарез нужно выделить пару страниц памяти ядра. Лезем в исходники ядра, файл {{#svn:/kernel/trunk/core/exports.inc|core/exports.inc}}, просматриваем экспортируемые имена (они осмысленны) и видим szKernelAlloc. Пролистываем вниз до метки kernel_export и ищем szKernelAlloc - обнаруживаем, что ему соответствует процедура kernel_alloc. Теперь ищем реализацию kernel_alloc, она обнаруживается в {{#svn:/kernel/trunk/core/heap.inc|core/heap.inc}}. Комментариев около функции нет, но есть объявление proc, из которого следует, что функция принимает один аргумент size типа dword. Теперь по названию ясно, что kernel_alloc выделяет память ядра в размере, равном единственному аргументу. Причём первые же три строчки кода функции показывают, что размер выравнивается вверх на границу 4096 (т.е. размер одной страницы), следовательно, функция выделяет некоторое целое количество страниц, а размер задаётся в байтах.

Дальше идёт процедура обработки запросов service_proc:

<syntaxhighlight lang="asm"> handle equ IOCTL.handle io_code equ IOCTL.io_code input equ IOCTL.input inp_size equ IOCTL.inp_size output equ IOCTL.output out_size equ IOCTL.out_size

align 4 proc service_proc stdcall, ioctl:dword

mov edi, [ioctl]
mov eax, [edi+io_code]

xor eax, eax ret endp

restore handle restore io_code restore input restore inp_size restore output restore out_size </syntaxhighlight>

Процедура обработки запросов вызывается, когда какой-то внешний код возжаждал общения именно с нашим драйвером. Это может быть как другой драйвер (формально драйвер может вызывать сам себя через механизм I/O, но смысла в этом нет), надыбавший где-то наш хэндл и вызвавший ServiceHandler, или даже само ядро (srv_handler, srv_handlerEx из {{#svn:/kernel/trunk/core/dll.inc|core/dll.inc}}), так и приложение функцией 68.17 (хэндл приложение может добыть при загрузке драйвера функцией 68.16). Нулевое возвращаемое значение означает успех, ненулевое соответствует ошибке. Вначале определяем сокращённые имена для членов структуры, описывающей запрос. В поле handle содержится хэндл драйвера (такой же, как и возвращаемое значение RegService), io_code - dword-идентификатор запроса, остальные поля вопросов вызывать не должны. Возвращаемое значение напрямую передаётся вызвавшему нас коду (драйвер/ядро/приложение). В конце восстанавливаем значения, переназначенные было на короткие имена членов структуры. В данном случае это без надобности, но в случае сложных драйверов короткие имена типа "input" запросто могут встречаться не один раз.

Дальше в {{#svn:/kernel/trunk/drivers/sceletone.asm|sceletone.asm|450}} содержится код поиска заданного оборудования на PCI-шине, нам он без надобности, при необходимости разберитесь сами.

Итак, с каркасом драйвера разобрались. А теперь будем писать свой драйвер. Начало стандартное:

<syntaxhighlight lang="asm">

;;
Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;;
Distributed under terms of the GNU General Public License  ;;
;;
FileMon
driver part

format MS COFF

include 'proc32.inc' include 'imports.inc' </syntaxhighlight>

Ориентируемся на {{#svn_rev:450}} (для 0.6.5.0 нужно было бы писать "new_app_base equ 0x60400000"):

<syntaxhighlight lang="asm"> OS_BASE equ 0; new_app_base equ 0x80000000 </syntaxhighlight>

Небольшая порция объявлений:

<syntaxhighlight lang="asm"> struc IOCTL { .handle dd ?

  .io_code     dd ?
  .input       dd ?
  .inp_size    dd ?
  .output      dd ?
  .out_size    dd ?

}

virtual at 0

 IOCTL IOCTL

end virtual

public START public version

DRV_ENTRY equ 1 DRV_EXIT equ -1

section '.flat' code readable align 16 </syntaxhighlight>

Пока что всё стереотипно. Но прежде чем писать код, нужно определиться, чего мы от этого кода хотим. Итак, наш драйвер будет понимать четыре кода запроса ввода/вывода. Код 0 для всех драйверов специально предназначен для получения версии драйвера (здесь уже идёт работа с версией самого драйвера, а не версией интерфейса ядра). Строго говоря, реализовывать обработку этого запроса необязательно (ядру глубоко наплевать на версию драйвера), но весьма желательно, поскольку практически всегда драйвер можно развивать и изменять, а тогда для кода, использующего наш драйвер, знать версию всегда полезно. Версию драйвера можно возвращать в любом формате, в нашем примере для простоты будем использовать просто dword-номер версии, равный 1. Далее, код 1 означает "начать лог", код 2 - "выдать лог до текущего момента и сбросить", код 3 - "остановить лог". Лог будет записываться во внутренний буфер размера 16 Кб (учитывая, что опрашивать драйвер мы будем раз в секунду, этого должно хватить за глаза, но если вдруг не хватит, присутствие лишних записей мы сигнализируем, но сами записи не принимаем). Формат выдаваемой информации о логе: вначале общий размер записанных данных лога; потом байт со значением 0 или 1, причём 1 означает, что какие-то записи не поместились в буфере; потом массив структур переменного размера, первый байт которых содержит номер вызванной функции файловой системы и определяет дальнейшее содержимое записи. Инициализировать что-либо в драйвере нам не надо, так что процедура START выглядит так:

<syntaxhighlight lang="asm"> proc START stdcall, state:dword

          cmp [state], DRV_ENTRY
          jne .exit

.entry:

          stdcall RegService, my_service, service_proc

ret .fail: .exit:

          xor eax, eax
          ret

endp </syntaxhighlight>

Далее - обработка запросов:

<syntaxhighlight lang="asm"> handle equ IOCTL.handle io_code equ IOCTL.io_code input equ IOCTL.input inp_size equ IOCTL.inp_size output equ IOCTL.output out_size equ IOCTL.out_size

proc service_proc stdcall, ioctl:dword mov edi, [ioctl] mov eax, [edi+io_code] test eax, eax jz .getversion dec eax jz .startlog dec eax jz .getlog dec eax jz .endlog xor eax, eax ret .getversion: cmp [edi+out_size], 4 jb .err mov edi, [edi+output] mov dword [edi], 1 ; version of driver .ok: xor eax, eax ret .err: or eax, -1 ret .startlog: mov al, 1 xchg al, [bLogStarted] test al, al jnz .ok mov [logptr], logbuf call hook jnc .ok mov [bLogStarted], 0 jmp .err .getlog: cli mov esi, logbuf mov ecx, [logptr] sub ecx, esi add ecx, 5 cmp ecx, [edi+out_size] jbe @f mov ecx, [edi+out_size] mov [bOverflow], 1 @@: sub ecx, 5 xor eax, eax xchg al, [bOverflow] mov edi, [edi+output] mov [edi], ecx add edi, 4 stosb rep movsb mov [logptr], logbuf sti xor eax, eax ret .endlog: xchg al, [bLogStarted] test al, al jz @f call unhook @@: xor eax, eax ret endp

restore handle restore io_code restore input restore inp_size restore output restore out_size </syntaxhighlight>

Здесь стоит отметить, что входных данных для драйвера не нужно, поля input/inp_size мы не используем. В поле out_size вызывающий код должен поместить размер буфера output. Если нас вызывает приложение, то все манипуляции с переводом указателей приложения в указатели ядра осуществляет ядро, а в структуре ioctl передаются уже подправленные указатели.

Ну а теперь часть, отвечающая за взаимодействие с драйверной подсистемой, закончилась и начинается собственно работа. Мы перехватываем функции файловой системы 6,32,33,58,70. Для этого мы используем тот факт, что общий обработчик int 0x40 вызывает конкретную функцию косвенным вызовом из таблицы servetable (код этот обработчика располагается в файле {{#svn:/kernel/trunk/core/syscall.inc|core/syscall.inc}}). Следовательно, если подменить нужные элементы в этой таблице на адреса наших обработчиков, то вызываться будет наш код. Узнать адрес servetable можно сканированием кода обработчика int 0x40. Адрес функции i40 легко узнать из IDT, а команда вызова в текущей реализации имеет вид "call dword [servetable+edi*4]", в машинном коде "FF 14 BD <servetable>" (в принципе никто не гарантирует, что так будет и дальше, в частности, потенциально возможна замена edi на eax; тогда нужно будет соответственно менять код).

<syntaxhighlight lang="asm"> hook: cli sub esp, 6 sidt [esp] pop ax ; limit pop eax ; base mov edx, [eax+40h*8+4] mov dx, [eax+40h*8]

edx contains address of i40

mov ecx, 100 .find: cmp byte [edx], 0xFF jnz .cont cmp byte [edx+1], 0x14 jnz .cont cmp byte [edx+2], 0xBD jz .found .cont: inc edx loop .find sti mov esi, msg_failed call SysMsgBoardStr stc ret .found: mov eax, [edx+3]

eax contains address of servetable

mov [servetable_ptr], eax mov edx, newfn06 xchg [eax+6*4], edx mov [oldfn06], edx mov edx, newfn32 xchg [eax+32*4], edx mov [oldfn32], edx mov edx, newfn33 xchg [eax+33*4], edx mov [oldfn33], edx mov edx, newfn58 xchg [eax+58*4], edx mov [oldfn58], edx mov edx, newfn70 xchg [eax+70*4], edx mov [oldfn70], edx sti clc ret

unhook: cli mov eax, [servetable_ptr] mov edx, [oldfn06] mov [eax+6*4], edx mov edx, [oldfn32] mov [eax+32*4], edx mov edx, [oldfn33] mov [eax+33*4], edx mov edx, [oldfn58] mov [eax+58*4], edx mov edx, [oldfn70] mov [eax+70*4], edx sti ret </syntaxhighlight>

Две вспомогательные функции:

<syntaxhighlight lang="asm"> write_log_byte:

in
al=byte

push ecx mov ecx, [logptr] inc ecx cmp ecx, logbuf + logbufsize ja @f mov [logptr], ecx mov [ecx-1], al pop ecx ret @@: mov [bOverflow], 1 pop ecx ret

write_log_dword:

in
eax=dword

push ecx mov ecx, [logptr] add ecx, 4 cmp ecx, logbuf + logbufsize ja @f mov [logptr], ecx mov [ecx-4], eax pop ecx ret @@: mov [bOverflow], 1 pop ecx ret </syntaxhighlight>

При написании самих обработчиков следует учитывать, что регистры циклически сдвигаются по сравнению с вызовом int 0x40 в приложении и что все указатели - это указатели 3-кольца.

<syntaxhighlight lang="asm"> newfn06: cli push [logptr] push eax mov al, 6 ; function 6 call write_log_byte mov eax, ebx ; start block call write_log_dword mov eax, ecx ; number of blocks call write_log_dword mov eax, edx ; output buffer call write_log_dword pop eax push eax push esi lea esi, [eax+new_app_base] ; pointer to file name @@: lodsb call write_log_byte test al, al jnz @b pop esi pop eax cmp [bOverflow], 0 jz .nooverflow pop [logptr] jmp @f .nooverflow: add esp, 4 @@: sti jmp [oldfn06]

newfn32: cli push [logptr] push eax mov al, 32 ; function 32 call write_log_byte pop eax push eax push esi lea esi, [eax+new_app_base] ; pointer to file name @@: lodsb call write_log_byte test al, al jnz @b pop esi pop eax cmp [bOverflow], 0 jz .nooverflow pop [logptr] jmp @f .nooverflow: add esp, 4 @@: sti jmp [oldfn32]

newfn33: cli push [logptr] push eax mov al, 33 ; function 33 call write_log_byte mov eax, ebx ; input buffer call write_log_dword mov eax, ecx ; number of bytes call write_log_dword pop eax push eax push esi lea esi, [eax+new_app_base] ; pointer to file name @@: lodsb call write_log_byte test al, al jnz @b pop esi pop eax cmp [bOverflow], 0 jz .nooverflow pop [logptr] jmp @f .nooverflow: add esp, 4 @@: sti jmp [oldfn33]

newfn58: cli push [logptr] push eax push ebx lea ebx, [eax+new_app_base] mov al, 58 ; function 58 call write_log_byte

dump information structure

mov eax, [ebx] call write_log_dword mov eax, [ebx+4] call write_log_dword mov eax, [ebx+8] call write_log_dword mov eax, [ebx+12] call write_log_dword push esi lea esi, [ebx+20] ; pointer to file name @@: lodsb call write_log_byte test al, al jnz @b pop esi pop ebx pop eax cmp [bOverflow], 0 jz .nooverflow pop [logptr] jmp @f .nooverflow: add esp, 4 @@: sti jmp [oldfn58]

newfn70: cli push [logptr] push eax push ebx lea ebx, [eax+new_app_base] mov al, 70 ; function 70 call write_log_byte

dump information structure

mov eax, [ebx] call write_log_dword mov eax, [ebx+4] call write_log_dword mov eax, [ebx+8] call write_log_dword mov eax, [ebx+12] call write_log_dword mov eax, [ebx+16] call write_log_dword push esi lea esi, [ebx+20] ; pointer to file name lodsb test al, al jnz @f lodsd lea esi, [eax+new_app_base+1] @@: dec esi @@: lodsb call write_log_byte test al, al jnz @b pop esi pop ebx pop eax cmp [bOverflow], 0 jz .nooverflow pop [logptr] jmp @f .nooverflow: add esp, 4 @@: sti jmp [oldfn70] </syntaxhighlight>

На этом код заканчивается. Теперь используемые данные (мы ориентируемся на {{#svn_rev:450}}, для 0.6.5.0 version должна быть 0x00030003):

<syntaxhighlight lang="asm"> version dd 0x00040004 my_service db 'fmondrv',0

msg_failed db 'Cannot hook required functions',13,10,0

section '.data' data readable writable align 16

servetable_ptr dd ?

oldfn06 dd ? oldfn32 dd ? oldfn33 dd ? oldfn58 dd ? oldfn70 dd ?

logptr dd ? logbufsize = 16*1024 logbuf rb logbufsize

bOverflow