Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Обработка исключений и сообщений
У кнопки Reset есть один недостаток: маленькая очень, кулаком попасть сложно.

На сей раз я наваял приложение, использующее несколько более продвинутые технологии, предоставляемые 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 функции.

А пока IDA делает грязную работу за нас, можно предаться чтению документации ( весьма рекомендую заниматься этим время от времени - можно узнать столько интересного :-). Итак, что мы можем узнать из официальной документации по Delphi о тонкой разнице между динамическими (dynamic) и виртуальными (virtual) методами ?

Virtual методы расположены в таблице виртуальных методах, по традиции называемой VTBL, которая дублируется для каждого производного класса. Если в производном классе переопределяется новый метод, указатель на него будет в этой таблице под тем же индексом, что и в VTBL класса-предка - но указывать он будет на перегруженный метод. За счёт этого достигается наилучшая скорость - вызов функции по указателю через смещение в VTBL. С другой стороны, для каждого нового класса полностью дублируется вся VTBL ! Короче, классический случай ножниц "скорость против размера".

Dynamic методы имеют несколько другой способ хранения. Им назначается некоторый индекс - но не в таблице, а в hash-структуре. Также эта структура не дублируется для каждого производного класса - если переопределяется dynamic метод, он переопределяется для данного класса - и всё. Но вызов dynamic методов имеет больше накладных расходов - при вызове Delphi просматривает все классы-предки данного класса в поисках метода с нужным индексом.

Посмотрим, как всё вышесказанное выглядит на Ассемблере:

Я надеюсь, Вы ещё помните, насколько полезна бывает RTTI ? RTTI класса нашей единственной формы расположена по адресу 0x44016C. На сей раз она содержит по смещению 1Ch ненулевое значение, а указатель на hash-массив dynamic методов. Структура эта имеет примерно такой вид:

СмещениеТипОписание
0WORDРазмер N hashа
2WORDN слов - индексов dynamic методов
N * 2 + 2DWORDN указателей на функции

Что ещё более интересно, в нашём случае индекс единственной функции - WMSizing 0x214. Если Вы посмотрите в файле заголовков messages.pas, 0x214 ( eq 532 ) есть значение сообщения WM_SIZING. В Borlandе, видимо, простые парни работают...

Итак, сейчас у нас есть более полное описание RTTI, я позволю себе повторить его здесь полностью:

СмещениеТипОписание
0DWORDуказатель на VTBL
4DWORDзначение не выяснено (vmtIntfTable)
8DWORDзначение не выяснено (vmtAutoTable)
ChDWORDзначение не выяснено (vmtInitTable)
10hDWORDуказатель на список наследований
14hDWORDуказатель на компоненты, которыми владеет данный класс
18hDWORDуказатель на массив обработчиков событий
1ChDWORDуказатель на hash dynamic методов
20hDWORDуказатель на Pascal-строку - имя класса
24hDWORDразмер класса
28hDWORDуказатель на структуру RTTI класса-предка данного класса
2ChDWORDуказатель на метод SafeCallException
30hDWORDуказатель на метод AfterConstruction
34hDWORDуказатель на метод BeforeDestruction
38hDWORDуказатель на метод Dispatch
3ChDWORDуказатель на метод DefaultHandler
40hDWORDуказатель на метод NewInstance
44hDWORDуказатель на метод FreeInstance
48hDWORDуказатель на метод Destroy
4ChDWORDsначало VTBL

Давайте рассмотрим самую примечательную функцию в моей программе - TForm1.Button1Click. Примечательна она исключительно тем, что вызывает функцию BuggyOne, выбрасывающую исключение, которое затем сама же и ловит двумя руками.


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


Обратите внимание на две вещи:

1) инициализация стека исключений. В стек помещается указатель 0x44064f, далее значение стека заносится с fs:[0]. По адресу 0x44064f нет ничего примечательного - просто переход на одинаковый для всех кусок кода HandleFinally. Но вот сразу за ним идёт код, который, казалось бы, никогда не достигается - переход на 0x440646. Но вот этот-то код как раз и есть finally часть в обработке исключений. В данном случае - это освобождение строки var_4.

HandleFinally:


@@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

Т.е. происходит следующее - инструкция следом за переходом на HandleFinally является процедурой обработки finally-части ( так как размер инструкции "jmp HandleFinally" равен ровно 5 байт )

2) Загрузка строки из ресурса. Строка BuggyOneCaption описана как resourcestring - это значит, что Delphi помесила её в строковую таблицу. Прототип функции LoadResString ( из Sys/System.pas ):


type
  PResStringRec = ^TResStringRec;
  TResStringRec = record
    Module: ^Longint;
    Identifier: Integer;
  end;

function LoadResString(ResStringRec: PResStringRec): string;

Module - handler загруженного модуля, содержащего в себе ресурс. Для нашей программы это hInstance самого приложения ( поскольку главная форма находится в том же модуле, что и объект TApplication ).

Identifier - целое число, меньшее 65536, или указатель на LPSZ строку - имя ресурса. По адресу 0x440368 содержится:


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 - адресом нашего класса.

Пожалуй, в BuggyOne больше нет ничего интересного.

Button1Click


...
        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. Следом за ней расположен массив фильтров исключений. Он имеет весьма незатейливую структуру:

СмещениеТипОписание
0DWORDРазмер N массива фильтров исключений
4DWORDУказатель на RTTI класса - объекта исключение
8DWORDУказатель на код, вызываемый при исключении этого класса
4 + M * 4DWORDУказатель на M-ную RTTI класса - объекта исключение
8 + M * 4DWORDУказатель на код, вызываемый при исключении M-ного класса

Далее мы можем наблюдать вызовы virtual & dynamic функций - соответственно, TRPExceptionChild::Old_one_virtual & TRPExceptionChild::Old_one_dynamic.

1) Вызов TRPExceptionChild::Old_one_virtual Простой и понятный вызов функции по указателю. Первым членом любого класса идёт указатель на VTBL - по смещению 0x0; метод Old_one_virtual является единственным в VTBL класса TRPExceptionChild - соответственно, он расположен под индексом 0. Под тем же самым индексом в VTBL класса TRPException расположен виртуальный метод TRPException::Old_one_virtual

2) Вызов TRPExceptionChild::Old_one_dynamic В hash dynamic методов класса TRPException метод TRPException::Old_one_dynamic прописан под индексом 0xFFFF. Сложно сказать, что будет, если, имея dynamic метод с индексом 0xFFFF, Вы попробуете обработать событие Windows с номеров 0xFFFF ( можете попробовать сделать это самостоятельно ). Остаётся надеятся, что Delphi всё-таки отслеживают занятые индексы для динамических методов. Как видите, для вызова динамических методов используется вызов функции CallDynaInst с передаваемым в регистре bx индексом метода.

Приложение A

Поскольку я человек ленивый ( лень - двигатель прогресса ) и мне совершенно не хотелось вручную сообщать IDA Pro, что это не просто нечто бесформенное, а самая что ни наесть структура RTTI ( при этом ещё мучительно вспоминая, чего там идёт под каким смещением ), я написал небольшой script на IDC, который позволяет мне иметь немного свободного времени для прямых обязанностей сисадмина, а именно - для чтения newsов и взлома программ...


//
// This script deal with Delphi RTTI structures
//
// 	Red Plait, 23-VIII-1999
//

#include 

// 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

Я даже в кои-то веки раз комментарии кое-где сделал, так что я надеюсь, Вам не составит труда разобраться в моих каракулях.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay