Дышит река, Кости стрелка Встретят закат у рудника. Дети войны, вам, Красные сны, вам, Тени стены, вам. Он мертвый, он мертвый, Мертвый охотник на мертвых поднимет ружье. Аукцыон, "Охотник", альбом "Как я стал предателем" 1989 |
Итак, продолжим. На сей раз я наваял приложение, использующее несколько более продвинутые технологии, предоставляемые Delphi - exceptions handling ( перехват исключений ), virtual & dynamic функции, обработку формой сообщений Windows, производные классы и загрузку строковых ресурсов из реестра. Исходный код моей программы мог бы выглядеть как-нибудь так:
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TRPEnum = ( RP_One, RP_Two, RP_Tree ); TRPEnumSet = set of TRPEnum; TRPException = class(Exception) private RP_Array: array[7..9] of string; Code: TRPEnumSet; public Procedure Old_one_virtual; virtual; Procedure Old_one_dynamic; dynamic; Constructor Create; Destructor Destroy; override; end; TRPExceptionChild = class(TRPException) Procedure Old_one_virtual; override; Procedure Old_one_dynamic; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } FDesignedWidth, FDesignedHeight: Integer; procedure BuggyOne; Procedure WMSizing( var Message: TMessage ); message WM_SIZING; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} resourcestring BuggyOneCaption = 'BuggyOne'; MalformedException = 'Malformed exception'; (* TRPException *) Constructor TRPException.Create; begin Application.MessageBox('Create', 'TRPException', ID_OK); inherited Create('BuggyOne object'); end; Destructor TRPException.Destroy; begin Application.MessageBox('Destroy', 'TRPException', ID_OK); Inherited; end; Procedure TRPException.Old_one_virtual; begin Application.MessageBox('Old_one_virtual','TRPException', ID_OK); end; Procedure TRPException.Old_one_dynamic; begin Application.MessageBox('Old_one_dynamic','TRPException', ID_OK); end; (* TRPExceptionChild *) Procedure TRPExceptionChild.Old_one_virtual; begin Application.MessageBox('Old_one_virtual','TRPExceptionChild', ID_OK); end; Procedure TRPExceptionChild.Old_one_dynamic; begin Application.MessageBox('Old_one_dynamic','TRPExceptionChild', ID_OK); end; (* TForm1 *) procedure TForm1.BuggyOne; var RP_E: TRPExceptionChild; N: Integer; begin MessageDlg(BuggyOneCaption,mtConfirmation,[mbOk],0); try RP_E := TRPExceptionChild.Create; RP_E.Code := [RP_One]; RP_E.RP_Array[7] := 'Seven'; N := 9; RP_E.RP_Array[8] := 'Eight'; RP_E.RP_Array[N] := 'Nine inch nails'; RP_E.Code := RP_E.Code + [RP_Two]; Raise RP_E; MessageDlg('Not will showed at the end',mtConfirmation,[mbOk],0); finally MessageDlg('In finally part',mtConfirmation,[mbOk],0); end; MessageDlg('Not will showed at the end',mtConfirmation,[mbOk],0); end; procedure TForm1.Button1Click(Sender: TObject); begin MessageDlg('Button1Click',mtConfirmation,[mbOk],0); try BuggyOne; except on E:TRPException do begin MessageDlg('Button1Click in exception block',mtConfirmation,[mbOk],0); E.Old_one_virtual; E.Old_one_dynamic; end; on E:TRPExceptionChild do begin MessageDlg(MalformedException,mtConfirmation,[mbOk],0); end; end; MessageDlg('Button1Click at the end',mtConfirmation,[mbOk],0); end; Procedure TForm1.WMSizing( var Message: TMessage ); var PRect : ^TRect; Begin PRect := Pointer (Message . LParam ); if PRect^. Right - PRect^. Left < FDesignedWidth then begin if Message.WParam in [ WMSZ_BOTTOMLEFT, WMSZ_LEFT, WMSZ_TOPLEFT ] then PRect^.Left := PRect^ . Right - FDesignedWidth else PRect^.Right := PRect^ . Left + FDesignedWidth; end; if PRect^ . Bottom - Prect^.Top < FDesignedHeight then begin if Message . WParam in [ WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT ] then PRect^.Top := PRect^ . Bottom - FDesignedHeight else PRect^. Bottom := PRect^ . Top + FDesignedHeight; end; End; procedure TForm1.FormCreate(Sender: TObject); begin FDesignedWidth := Width; FDesignedHeight := Height; MessageDlg('FormCreate',mtConfirmation,[mbOk],0); end;Не вершина программистского мастерства, конечно, но для наших целей вполне годится. Итак, запустим дизассемблер ( я использовал IDA 3.8b ) и не забудьте применить файл сигнатур для библиотеки VCL версии 4 ( d4vcl ) - в моём случае IDA опознала 2172 функции.
Смещение | тип | описание |
0 | WORD | Размер N hashа |
2 | WORD | N слов - индексов dynamic методов |
N * 2 + 2 | DWORD | N указателей на функции |
Что ещё более интересно, в нашём
случае индекс единственной функции - WMSizing
0x214. Если Вы посмотрите в файле заголовков
messages.pas, 0x214 ( eq 532 ) есть значение сообщения
WM_SIZING. В Borlandе, видимо, простые парни
работают...
Итак, сейчас у нас есть более полное
описание RTTI, я позволю себе повторить его
здесь полностью:
смещение | тип | описание |
0 | DWORD | указатель на VTBL |
4 | DWORD | значение не выяснено (vmtIntfTable) |
8 | DWORD | значение не выяснено (vmtAutoTable) |
Ch | DWORD | значение не выяснено (vmtInitTable) |
10h | DWORD | указатель на список наследований |
14h | DWORD | указатель на компоненты, которыми владеет данный класс |
18h | DWORD | указатель на массив обработчиков событий |
1Ch | DWORD | указатель на hash dynamic методов |
20h | DWORD | указатель на Pascal-строку - имя класса |
24h | DWORD | размер класса |
28h | DWORD | указатель на структуру RTTI класса-предка данного класса |
2Ch | DWORD | указатель на метод SafeCallException |
30h | DWORD | указатель на метод AfterConstruction |
34h | DWORD | указатель на метод BeforeDestruction |
38h | DWORD | указатель на метод Dispatch |
3Ch | DWORD | указатель на метод DefaultHandler |
40h | DWORD | указатель на метод NewInstance |
44h | DWORD | указатель на метод FreeInstance |
48h | DWORD | указатель на метод Destroy |
4Ch | DWORDs | начало VTBL |
BuggyOne proc near var_4 = dword ptr -4 push ebp mov ebp, esp push 0 ; инициализация в 0 var_4 push ebx ; сохранить ebx push esi ; и esi xor eax, eax push ebp ; и ещё ebp push offset loc_0_44064F ; поместим в стек адрес ; finally кода push dword ptr fs:[eax] ; и прежнее значение стека ; обработки исключений mov fs:[eax], esp ; в стек обработки исключений ; помещается указатель на текущее значение стека push 0 lea edx, [ebp+var_4] ; загрузим в var_4 строку из mov eax, offset off_0_440368 ; ресурсов call @LoadResString ... loc_0_440646: lea eax, [ebp+var_4] ; очистить строку в var_4 call @@LStrClr ; ::`intcls'::LStrClr retn ; ------------------------------------------------------------------ loc_0_44064F: jmp @@HandleFinally ; ::`intcls'::HandleFinally ; ------------------------------------------------------------------ jmp short loc_0_440646Обратите внимание на две вещи:
@@HandleFinally: mov eax, [esp+4] mov edx, [esp+8] test dword ptr [eax+4], 6 jz short loc_0_403294 mov ecx, [edx+4] ; адрес перехода на HandleFinally mov dword ptr [edx+4], offset loc_0_403294 push ebx push esi push edi push ebp mov ebp, [edx+8] add ecx, 5 ; добавим к нему 5 call @System@_16583 ; System::_16583 call ecx ; и вызовем как функцию pop ebp pop edi pop esi pop ebx loc_0_403294: mov eax, 1 retnНа момент вылета на этот код в fs:[0] и eax содержится указатель стека, в котором находятся ранее занесённые в него ( смотрите начало процедуры BuggyOne; также я привык изображать вершину стека сверху, а не как оно есть на самом деле ):
[eax + 4] прежнее значение стека в fs:[0]
[eax + 8] указатель на инструкцию
перехода к HandleFinally
| [eax + 0xC] ebp | |
type PResStringRec = ^TResStringRec; TResStringRec = record Module: ^Longint; Identifier: Integer; end; function LoadResString(ResStringRec: PResStringRec): string;Module - handler загруженного модуля, содержащего в себе ресурс. Для нашей программы это hInstance самого приложения ( поскольку главная форма находится в том же модуле, что и объект TApplication ).
off_0_440368 dd offset dword_0_4424D8 ; hModule приложения dd 0FF5Dh ; IdentifierЧисло 0xFF5D = 65373 < 65536, так что наша строка идентифицируется по числовому значению. Посмотрим ресурсы моей программы в редакторе ресурсов Restorator ( кстати, весьма рекомендую эту программу для исследований приложений на Delphi - она умеет показывать описание Delphi-форм ! ). Наша строка нашлась в секции string tables под номером секции 4086, смещение -3. Как это соотносится с ранее найденным значением идентификатора ? Очень просто:
65373 = 4086 * 16 - 3;Всё гениальное просто ( однако не всё простое гениально ).
xor eax, eax push ebp push offset loc_0_44061D ; новый finally handler push dword ptr fs:[eax] mov fs:[eax], esp mov dl, 1 mov eax, ds:off_0_44016C ; ptr to TRPExceptionChild RTTI call sub_0_440378 ; TRPExceptionChild::Create mov ebx, eax mov al, ds:byte_0_440660 ; db 1 eq RP_One из TRPEnum mov [ebx+18h], al lea eax, [ebx+0Ch] mov edx, offset aSeven ; "Seven" call @@LStrAsg ; ::`intcls'::LStrAsg mov esi, 9 lea eax, [ebx+10h] mov edx, offset aEight ; "Eight" call @@LStrAsg ; ::`intcls'::LStrAsg lea eax, [ebx+esi*4-10h] mov edx, offset aNineInchNails ; "Nine inch nails" call @@LStrAsg ; ::`intcls'::LStrAsg mov al, [ebx+18h] or al, ds:byte_0_44069C ; db 2 eq RP_Two из TRPEnum mov [ebx+18h], al mov eax, ebx call @@RaiseExcept ; ::`intcls'::RaiseExcept ... loc_0_44061D: jmp @@HandleFinally ; ---------------------------------------- jmp short loc_0_440607 ... loc_0_440607: push 0 mov cx, ds:word_0_44065C mov dl, 3 mov eax, offset aInFinallyPart call @MessageDlg retnДальше совсем просто. Инициализируется новый обработчик finally части, при этом указатель на старое значение стека также помещается в стек. Далее вызывается конструктор TRPExceptionChild::Create - первым аргументом ему передаётся указатель на RTTI класса TRPExceptionChild, а вторым ( в регистре dl, я не знаю для чего ) 1 - указатель на созданный экземпляр класса возвращается в регистре eax, и затем пересылается в ebx, который используется в дальнейшем как базовый регистр. Члену Code присваивается значение RP_One ( eq 1 ) из набора TRPEnum. Можно заметить, что Code расположена в классе TRPException по смещению 0x18h. Затем идёт присваивание значений массиву строк - массив начинается по смещению 0xC. Довольно непонятно выглядит присваивание последнему ( 9ому элементу массива ): он должен быть расположен по смещению 0xC + (3 - 1) * 4 = 0x14; 9 * 4 - 0x10 даёт то же самое 0x14, но какова логика ! Затем к нашему набору Code добавляется RP_Two ( eq 2 ). Потом вызывается процедура RaiseExcept с единственным аргументом в eax - адресом нашего класса.
... push offset loc_0_440728 push dword ptr fs:[edx] mov fs:[edx], esp mov eax, ebx call BuggyOne ; процедура, генерирующая исключение xor eax, eax pop edx pop ecx pop ecx mov fs:[eax], edx jmp short loc_0_440790 ; -------------------------------------------------------- loc_0_440728: jmp @@HandleOnException ; ::`intcls'::HandleOnExceptions ; -------------------------------------------------------- dd 2 ; размер фильтров исключений dd offset off_0_4400F4 ; адрес RTTI TRPException dd offset loc_0_440741 ; адрес код для TRPException dd offset off_0_44016C ; TRPExceptionChild dd offset loc_0_44076B ; On TRPExceptionChild ; ---------------------------------------------------------------- loc_0_440741: mov ebx, eax push 0 mov cx, ds:word_0_4407C8 mov dl, 3 mov eax, offset aButton1clickIn ; строка "Button1Click in exception block" call @MessageDlg mov eax, ebx mov edx, [eax] ; вызов TRPExceptionChild::Old_one_virtual call dword ptr [edx] mov eax, ebx mov bx, 0FFFFh ; вызов TRPExceptionChild::Old_one_dynamic ; имеет индекс 0xFFFF call @@CallDynaInst ; ::`intcls'::CallDynaInst jmp short loc_0_44078BЗдесь можно увидеть в действии механизм фильтрации и обработки исключений. Опять в fs:[0] помещается указатель на стек, но на сей раз в него помещён адрес инструкции перехода к процедуре обработке исключений HandleOnExceptions. Следом за ней расположен массив фильтров исключений. Он имеет весьма незатейливую структуру:
Смещение | тип | описание |
0 | DWORD | Размер N массива фильтров исключений |
4 | DWORD | Указатель на RTTI класса - объекта исключение |
8 | DWORD | Указатель на код, вызываемый при исключении этого класса |
4 + M * 4 | DWORD | Указатель на M-ную RTTI класса - объекта исключение |
8 + M * 4 | DWORD | Указатель на код, вызываемый при исключении M-ного класса |
Поскольку я человек ленивый ( лень - двигатель прогресса ) и мне совершенно не хотелось вручную сообщать IDA Pro, что это не просто нечто бесформенное, а самая что ни наесть структура RTTI ( при этом ещё мучительно вспоминая, чего там идёт под каким смещением ), я написал небольшой script на IDC, который позволяет мне иметь немного свободного времени для прямых обязанностей сисадмина, а именно - для чтения newsов и взлома программ...
/* * This script deal with Delphi RTTI structures * * Red Plait, 23-VIII-1999 */ #include <idc.idc> // makes dword and offset to data static MakeOffset(adr) { auto ref_adr; MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeUnkn(adr+2,0); MakeUnkn(adr+3,0); MakeDword(adr); ref_adr = Dword(adr); if ( ref_adr != 0 ) add_dref(adr, ref_adr, 0); } // makes dword and offset to a function static MakeFOffset(adr,string) { auto ref_adr, func_name; MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeUnkn(adr+2,0); MakeUnkn(adr+3,0); MakeDword(adr); ref_adr = Dword(adr); if ( ref_adr != 0 ) { MakeFunction(ref_adr, BADADDR); MakeName(ref_adr,string); add_dref(adr,ref_adr,0); } } // makes simple string static do_Str(adr,len) { auto count; for ( count = 0; count < len; count++ ) MakeUnkn(adr + count,0); MakeStr(adr, adr+len); } // makes Pascal-style string static makePStr(adr) { auto len; MakeUnkn(adr,0); MakeByte(adr); len = Byte(adr); do_Str(adr+1,len); return len + 1; } // extract pascal-style string static getPStr(adr) { auto len, res, c; len = Byte(adr++); res = ""; for ( ; len; len-- ) { c = Byte(adr++); res = res + c; } return res; } // returns name of class of this RTTI static getRTTIName(adr) { auto ptr; ptr = Dword(adr+0x20); if ( ptr != 0 ) return getPStr(ptr); else return ""; } // processing owned components list static processOwned(adr) { auto count, str_len, comp_count, rtti_base; MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); comp_count = Word(adr); /* count of RTTI array */ adr = adr + 2; MakeOffset(adr); rtti_base = Dword(adr); /* offset to array of RTTI */ adr = adr + 4; /* process RTTI array */ MakeUnkn(rtti_base,0); MakeUnkn(rtti_base+1,0); MakeWord(rtti_base); /* size of array */ count = Word(rtti_base); rtti_base = rtti_base + 2; for ( str_len = 0; str_len < count; str_len++ ) { MakeOffset(rtti_base + str_len * 4); } /* process each of owned to form components */ for ( count = 0; count < comp_count; count++ ) { // offset in owners class MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); str_len = Word(adr); MakeComm(adr, "Offset 0x" + ltoa(str_len,0x10) ); adr = adr + 2; // unknow word MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); adr = adr + 2; // index in RTTI array MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); str_len = Word(adr); MakeComm(adr, "Type: " + getRTTIName(Dword(rtti_base + str_len*4)) ); adr = adr + 2; // pascal string - name of component MakeUnkn(adr,0); str_len = Byte(adr); adr = adr + 1; do_Str(adr,str_len); adr = adr + str_len; } } // process events handlers list static processHandlers(adr) { auto count, str_len, f_addr; MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); MakeComm(adr,"Handlers count"); count = Word(adr); adr = adr + 2; for ( ; count; count-- ) { // unknown dword MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); adr = adr + 2; // offset to function - handler f_addr = Dword(adr); MakeOffset(adr); adr = adr + 4; // Name of handler if ( f_addr != 0 ) { MakeCode(f_addr); MakeFunction(f_addr, BADADDR); MakeName(f_addr, getPStr(adr)); } adr = adr + makePStr(adr); } } // process inherited list first element ( may be recursive ? ) // returns pointer to next parent`s struct static processParent(adr) { auto str_len; auto res; res = 0; // 1st byte - unknown MakeUnkn(adr,0); MakeByte(adr); adr = adr + 1; // next - Pascal string - name of class adr = adr + makePStr(adr); // VTBL pointer MakeOffset(adr); adr = adr + 4; // next - pointer to pointer to next this struct :-) MakeOffset(adr); str_len = Dword(adr); if ( str_len != 0 ) { MakeOffset(str_len); res = Dword(str_len); } adr = adr + 4; // WORD - unknown MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); adr = adr + 2; // next - name of Unit name makePStr(adr); return res; } // process dynamic methods table static processDynamic(adr) { auto count, base, i, old_comm; MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); count = Word(adr); MakeComm(adr,"Count of dynamic methods " + ltoa(count,10) ); adr = adr + 2; base = adr + 2 * count; for ( i = 0; i < count; i++ ) { MakeUnkn(adr,0); MakeUnkn(adr+1,0); MakeWord(adr); MakeOffset(base + 4 * i); old_comm = Comment(base + 4 * i); if ( old_comm != "" ) MakeComm(base + 4 * i, "Dynamic 0x" + ltoa(Word(adr),0x10) + ", " + old_comm ); else MakeComm(base + 4 * i, "Dynamic 0x" + ltoa(Word(adr),0x10) ); adr = adr + 2; } return count; } // makes tricky VTBL entries static makeF2Offset(adr,name) { auto comm,ref_adr; MakeOffset(adr); ref_adr = Dword(adr); if ( ref_adr != 0 ) add_dref(adr,ref_adr,0); comm = Comment(adr); if ( comm != "" ) MakeComm(adr, comm + ", " + name); else MakeComm(adr, name); } // main function - process RTTI structure static processRTTI(adr) { auto count; auto res; auto my_name; my_name = ""; // first DWORD - VTBL pointer MakeOffset(adr); // three next DWORD is unknown MakeOffset(adr+4); MakeOffset(adr+8); MakeOffset(adr+0xc); // list of parents MakeOffset(adr+0x10); count = Dword(adr+0x10); if ( count != 0 ) // also process first parent for this class processParent(count); // 0x14 DWORD - owned components MakeOffset(adr+0x14); count = Dword(adr+0x14); if ( count != 0 ) processOwned(count); // 0x18 DWORD - event handlers list MakeOffset(adr+0x18); count = Dword(adr+0x18); if ( count != 0 ) processHandlers(count); // 0x1c DWORD - pointer to dynamic functions list MakeOffset(adr+0x1c); count = Dword(adr+0x1c); if ( count != 0 ) { count = processDynamic(count); MakeComm(adr+0x1c, ltoa(count,10) + " dynamic method(s)"); } // 0x20 DWORD - pointer to class name MakeOffset(adr+0x20); count = Dword(adr+0x20); if ( count != 0 ) { makePStr(count); my_name = getPStr(count); MakeComm(adr+0x20, "Name: " + my_name ); } // 0x24 DWORD - size of class MakeUnkn(adr+0x24,0); MakeUnkn(adr+0x25,0); MakeUnkn(adr+0x26,0); MakeUnkn(adr+0x27,0); MakeDword(adr+0x24); MakeComm(adr+0x24,"Size of class"); // 0x28 - pointer to parent`s RTTI struct MakeOffset(adr+0x28); res = Dword(adr+0x28); MakeComm(adr+0x28,"Parent`s class"); // 0x2c SafeCallException makeF2Offset(adr+0x2c,my_name + "::SafeCallException"); // 0x30 AfterConstruction makeF2Offset(adr+0x30,my_name + "::AfterConstruction"); // 0x34 BeforeConstruction makeF2Offset(adr+0x34,my_name + "::BeforeConstruction"); // 0x38 Dispatch makeF2Offset(adr+0x38,my_name + "::Dispatch"); // 0x3C DefaultHandler makeF2Offset(adr+0x3c,my_name + "::DefaultHandler"); // 0x40 NewInstance makeF2Offset(adr+0x40,my_name + "::NewInstance"); // 0x44 FreeInstance makeF2Offset(adr+0x44,my_name + "::FreeInstance"); // 0x48 Destroy makeF2Offset(adr+0x48,my_name + "::Destroy"); return res; }Пояснения по каждой функции:
MakeOffset создаёт смещение по
указанному адресу adr. Иногда IDA бывает
упряма, и настаивает, что по этому адресу
вовсе не смещение, а, скажем, data - четырёх
вызовов MakeUnkn обычно бывает
достаточно, чтобы изменить её мнение
MakeFOffset - аналогично MakeOffset, но
только создаёт смещение на функцию,
которую называет string. Warning: не
проверяется результат MakeFunction,
поэтому функция может быть не совсем
правильной. В любом случае, это нужно
проверять обычно.
| do_Str - помечает len байт с адреса adr
как строку
| makePStr - создаёт pascal-строку по адресу adr.
Возвращает её длину, включая сам байт
длины.
| getPStr - возвращает значение pascal-строки
по адресу adr.
| getRTTIName - возвращает имя класса по его
RTTI, расположенной по адресу adr
| processOwned - обрабатывает список
компонентов, принадлежащих данному
компоненту. Сам список начинается с
адреса adr
| processHandlers - обрабатывает список
функций-обработчиков событий. Сам список
начинается с адреса adr. Warning: так
как имеет место попытка назвать функцию
так же, как она называлась в этом классе,
имя может быть неуникально ( вспомните,
сколько раз у Вас были функции с именем Button1Click
в разных классах )
| processParent - обрабатывает один элемент в
списке наследований по адресу adr. Это
рекурсивная структура, но её окончание
может быть обозначено по-разному - либо
как Nil, либо как две структуры TObject,
ссылающиеся друг на друга. На всякий
случай возвращается ссылка на следующий
элемент.
| processDynamic - обрабатывает hash
динамических методов по адресу adr. Warning:
несмотря на то, что этот метод пытается
сохранить ранее данные комментарии для
указателя на каждую dynamic функцию, похоже,
что в IDA Pro есть bug, из-за которого нельзя
извлечь комментарии для опознанных с
помощью сигнатур функций ( который по
умолчанию имеют тёмно-коричневый цвет,
скажем, TFormCustom::WMPaint ). Вызов Comment на таких
комментариях возвращает пустую строку.
Возвращается число динамических функций.
| makeF2Offset - вспомогательная функция,
служит по пометки служебных функций ( с
отрицательным индексом в VTBL ) и
добавления к ним комментария. Адрес
функции передаётся в adr, строка
комментария в name
| processRTTI - собственно, самая главная
функция, собирающая все остальные -
обрабатывает структуру RTTI по адресу adr | |