Изучаем ассемблер в Delphi

=== Изучаем ассемблер в Delphi ===
Vit - Печатается в DRKB с сокращениями
Основное предназначение этой статьи, заполнить пробелы в оригинальной документации по Borland Delphi Developer, при этом весь программный код, а так же теория, полностью совместимы со всеми версиями Delphi.
==== Использование Ассемблера в Борландовском Delphi ====
Перед тем, как начать, хотелось бы определиться с уровнем знаний, необходимых для нормального усвоения данного материала. Необходимо быть знакомым со встроенными средствами отладки в Delphi. Так же необходимо иметь представление о таких терминах как тип реализации (instantiation), null pointer и распределение памяти. Если в чём-то из вышеупомянутого Вы сомневаетесь, то постарайтесь быть очень внимательны и осторожны при воплощении данного материала на практике. Кроме того, будет обсуждаться только 32-битный код, так что понадобится компилятор не ниже Delphi 2.0.
==== Зачем использовать Ассемблер? ====
На мой взгляд, Object Pascal, это инструмент, позволяющий генерировать быстрый и эффективный код, однако использование ассемблера в некоторых случаях позволяет решать некоторые задачи более эффективно. За всю работу с Delphi, я пришёл к выводу, что использование низкоуровневого кода необходимо в двух случая.
# Обработка большого количества данных. Nb. В данный случай не входит ситуация, когда используется язык запроса данных.
# В высокоскоростных подпрограммах работы с дисплеем. Nb. Имеется ввиду использование простых процедур на чистом паскале, но никак не внешних библиотек и DirectX.
В конце статьи мы рассмотрим примеры, которые явно отражают значимость этих критериев, а так же не только когда и где использовать ассемблерные вставки, но и как включать такой код в Delphi.
==== Что такое Ассемблер? ====
Надеюсь, что Все читатели этой статьи имеют как минимум поверхностное представление о работе процессора. Грубо говоря, это калькулятор с большим объёмом памяти. Память, это не более чем упорядоченная последовательнось двоичных цифр. Каждая такая цифра является байтом. Каждый байт может содержать в себе значение от 0 до 255, а так же имеет свой уникальный адрес, при помощи которого процессор находит нужные значения в памяти. Процессор так же имеет набор регистров (это можно расценить как глобальные переменные). Например eax,ebx,ecx и edx, это универсальные 32-битные регистры. Это значит, что самое большое число, которое мы можем записать в регистр eax, это 2 в степени 32 минус 1, или 4294967295.
Как мы уже выяснили, процессор манипулирует значениями регистров. Машинный код операции прибавления 10 к значению регистра eax будет выглядеть следующим образом
 05/0a/00/00/00 
Однако, такая запись абсолютно не читабельна и, как следствие, не пригодна при отладке программы. Так вот Ассемблер, это простое представление машинных команд в более удобном виде. Теперь давайте посмотрим, как будет выглядеть прибавление 10 к eax в ассемблерном представлении:
add eax, 10 { a := a + 10 }
А вот так выглядит вычитание значения ebx из eax
sub eax,ebx {a := a - b } 
Чтобы сохранить значение, можно просто поместить его в другой регистр
mov eax, ecx { a := c }
или даже лучше, сохранить значение по определённому адресу в памяти
mov [1536], eax { сохраняет значение eax по адресу 1536 }
и конечно же взять его от туда
mov eax, [1536] 
Однако, тут есть важный момент, про который забывать не желательно. Так как регистр 32-битный(4 байта), то его значение будет записано сразу в четыре ячейки памяти 1536, 1537, 1538 и 1539.
А теперь давайте посмотрим, как компилятор преобразует действия с переменными в машинный код. Допустим у нас есть строка Count := 0; Для компилятора это означает, что надо просто запомнить значение. Следовательно, компилятор генерирует код, который сохраняет значение в памяти по определённому адресу и следит, чтобы не произошло никаких накладок, и обзывает этот адрес как 'Count'. Вот как выглядит такой код
mov eax, 0 
mov Count, eax 
Компилятор не может использовать строку типа
mov Count, 0 
из-за того, что как минимум один параметр инструкции должен являться регистром.
Если посмотреть на строку Count := Count + 1; 
то её ассемблерное представление будет выглядеть как
mov eax, Count 
add eax, 1 
mov Count, eax
Для переменных, тип которых отличается от целого, всё усложняется. Однако, рассмотрим эту тему немного позже, а сейчас предлагаю закрепить теорию практическими примерами.
Итак, рассмотрим первый пример. Сразу извинюсь за тривиальность, но с чего-то надо начинать.
function Sum(X, Y: Integer): Integer; 
begin 
 Result := X + Y; 
end; 
А вот так будет выглядеть операция сложения двух целых чисел на ассемблере:
function Sum(X,Y:integer):integer; 
begin 
 asm
 mov eax, X
 add eax, Y
 mov Result, eax
 end;
end;
Этот код прекрасно работает, однако он не даёт нам преимущества в скорости, а так же потерялось восприятие кода. Но не стоит огорчаться, так как те немногие знания, которые Вы почерпнули из этого материала, можно использовать с большей пользой. Допустим, нам необходимо преобразовать явные значения Red, Green, и Blue в цвета типа TColor, подходящие для использования в Delphi. Тип TColor описан как 24-битный True Colour хранящийся в формате целого числа, то есть четыре байта, старший из которых равен нулю, а далее по порядку красный, зелёный, синий.
function GetColour(Red, Green, Blue: Integer): TColor; 
begin 
 asm
{ ecx будет содержать значение TColor }
 mov ecx, 0 
{ начинаем с красной компоненты }
 mov eax, Red 
{ необходимо убедиться, что красный находится в диапазоне 0 <= Red <= 255 }
 and eax, 255 
{ сдвигаем значение красного в правильное положение }
 shl eax, 16 
{ выравниваем значение TColor }
 xor ecx, eax 
{ проделываем тоже самое с зелёным }
 mov eax, Green 
 and eax, 255
 shl eax, 8
 xor ecx, eax
{ и тоже самое с синим }
 mov eax, Blue 
 and eax, 255
 xor ecx, eax
 mov Result, ecx
 end;
end; 
Заметьте, что я использовал несколько бинарных операций. Эти операции также определены непосредственно в Object Pascal. 
Примечание * от Jin X
Чушь! Во-первых, параметры обязаны быть регистрами только в очень редких случаях (например, при чтении/записи из/в порт: out 20h,al), а во-вторых, компилятор Delphi7 генерирует именно mov Count,12345678h при использовании Count := $12345678. Но! Когда мы делаем Count := 0, то генерируется пара xor eax,eax + mov Count,eax , причём лишь в целях экономии памяти (такая запись короче в машинном представлении).
Примечание ** от Jin X
это тоже не есть правда, компилятор делает гораздо проще: inc Count
Source: Взято из http://forum.sources.ru
ID: 00290