четверг, 14 июля 2011 г.

Реализация вызовов методов компилятором

Это перевод Method calls compiler implementation. Автор: Hallvard Vassbotn.

Чтобы поддерживать множество возможностей языка, компилятор Delphi использует несколько различных структур данных и шаблонов генерации машинного кода. Чтобы помочь в понимании того, как реализуются компилятором возможности языка Delphi, чтобы было проще опознавать эти шаблоны и структуры при низкоуровневой отладке ассемблерного кода и чтобы было возможным понимать, что вы делаете при патчинге кода в run-time - я расскажу про некоторые из этих шаблонов в этом посте.

Это обсуждение затрагивает шаблоны, используемые компиляторами D7, D2005, D2006 и D2007 под Win32, но все эти шаблоны должны быть более-менее идентичны для всех других Win32-версий компиляторов Delphi.

Вызов невиртуального метода

Простейший случай - это когда компилятор генерирует код для вызова нормального, не виртуального метода или глобальной подпрограммы. Он использует инструкцию CALL процессора x86, которая требует немедленного относительного смещения - как части потока команд. Это означает, что нет никаких накладных расходов для расчетов или выборки целевого адреса из внешней памяти. Адрес является частью закодированной инструкции и процессор выполнит идеальный прогноз адреса цели перехода. Например:
type
  TFoo = class
    procedure Bar;
  end;

var
  Foo: TFoo;
begin
  Foo := TFoo.Create;
  Foo.Bar;
Генерируемый код для вызова Foo.Bar:
MOV EAX, [Foo] 
CALL <Относительное смещение TFoo.Bar>
Первая инструкция загружает неявный указатель Self в регистр EAX (по умолчанию весь код Delphi использует соглашение вызова register). Смещение в инструкции CALL относительно текущего значения регистра IP (Instruction Pointer - указатель на инструкцию кода). Это означает, что закодированный вызов одной и той же подпрограммы (но из разных мест одной функции) будет использовать различные смещения. Основная причина для использования смещений вместо более очевидных абсолютных адресов - уменьшить необходимость правок кода, если модуль (к примеру - DLL) перемещён в памяти (чаще всего - в результате загрузки по адресу, отличному от предпочитаемого базового).

Вызов виртуального метода

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

На уровне реализации компилятора Win32 виртуальные методы диспетчеризуются с помощью команды CALL, которая перенаправляет вызов через таблицу VMT объекта. Каждый виртуальный метод во время компиляции получает уникальный статический индекс (смещение VMT), связанный с ним. Вы не можете получить это смещение непосредственно с помощью кода на Паскале, но BASM теперь имеет директиву VMTOFFSET, чтобы дать возможность вызова виртуальных методов чистым способом (без хаков - прим.пер.). Вот пример вызова виртуального метода из BASM:
type
  TMyClass = class
    procedure Method; virtual;
  end;

procedure TMyClass.Method;
begin
  writeln(ClassName, '.Method');
end;

procedure CallMyMethod(Instance: TMyClass);
asm
  MOV ECX, [EAX]
  CALL [ECX + VMTOFFSET TMyClass.Method]
end;

var
  Instance: TMyClass;
begin
  Instance := TMyClass.Create;
  CallMyMethod(Instance);
  readln;
end.
Вот пример машинных кодов, участвующих в вызове виртуального метода:
// EAX содержит экземпляр объекта (Self)
MOV ECX, [EAX]    // Получить указатель на VMT в ECX 
CALL [ECX+0x014]  // Вызов виртуального метода через слот в VMT
В следующем посте мы углубимся в детали таблицы виртуальных методов, включая рассмотрение хака явного вызова через VMT.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.