Есть два вида оптимизации: структурная и локальная. В этой маленькой главе я затрону оба эти вида. Hо, во-первых, вы должны понять одну вещь: никогда не оптимизируйте ваш код, пока ваш код не будет полностью работать. Если вы начнете оптимизировать код, который не работает, вы наворотите еще больше ошибок.
Структурная оптимизация
Это самая эффективная и самая сложная для воплощения и понимания. Этот вид оптимизации можно легко понять, используя бумагу для зарисовки алгоритма вашего вируса. Здесь у нас нет, поэтому давайте представим, что вы, вернее ваш вирус, сначала открываете файл только для чтения, закрываете, открываете снова для чтения/записи и закрываете снова. Все это - потеря байтов. При структурной оптимизации вы должны хорошо продумать, что должен делать ваш вирус и что нет, дабы не тратить место попусту. Решение будет отличаться в зависимости от конкретной ситуации.
Локальная оптимизация
Это самый простой вид, хотя и с его помощью можно сэкономить множество байтов. Метод состоит в изменении некоторых линий на другие, которые делают то же самое, но занимают меньшее количество байт.
¤ Очищение регистров:
mov bx,0000h ; 3 байта
xor bx,bx ; 2 байта
sub bx,bx ; 2 байта
Hикогда не используйте первый способ, а используйте второй или третий. Один регистр (DX) можно очищать еще одним способом. Давайте посмотрим:
mov dx,0000h ; 3 байта
xor dx,dx ; 2 байта
sub dx,dx ; 2 байта
cwd ; Конвертируем слово в двойное слово
; (1 байт)
CWD будет работать, только если содержимое AX меньше, чем 8000h. Есть еще одни путь очистить AH за один байт: если AL < 80h, вы можете использовать инструкцию CBW.
¤ Сравнения:
Для сравнения, как известно, существует специальная инструкция. Для сравнения двух регистров вы можете использовать два способа:
cmp ax,bx ; 2 байта
xor ax,bx ; 2 байта
Hо XOR мы можем использовать только, если мы хотим узнать, равны ли значения. Тем не менее, мы можем использовать XOR вместо CMP, чтобы сохранить байт, если сравниваем регистр с числом:
cmp ax,0666h ; 3 байта
xor ax,0666h ; 2 байта
Но в силу природы инструкции XOR мы не можем использовать ее для того, чтобы узнать, пусть ли регистр. Здесь нас спасет OR...
¤ Оптимизированный регистр - AX:
cmp ax,0000h ; 3 байта
or ax,ax ; 2 байта
Вы можете использовать его для сравнений:
cmp bx,0666h ; 4 байта
cmp ax,0666h ; 3 байта
И вы можете перемещать содержимое AX в другой регистр оптимизированным образом:
mov bx,ax ; 2 байта
xchg ax,bx ; 1 байт
Вы можете это делать, если значения AX и BX для вас не важны. Это инструкция будет полезна для открытия файла, потому что файловый хэндл лучше держать в BX.
¤ Строковые операции:
Каждая из строковых команд (MOVS, STOS, SCAS...) - это оптимизированный способ выполнять определенные действия. Давайте посмотрим, для каких целей они могут пригодиться:
- MOVS: перемещение из позиции DS:[SI] в ES:[DI]
les di,ds:[si] ; 3 байта
movsb ; Если нам нужен байт (1 байт)
movsw ; Если нам нужно слово (1 байт)
movsd ; Если нам нужно двойное слово (2
; байта) 386+
- LODS: помещает в приемник значение в позиции DS:[SI]
mov ax,ds:[si] ; 2 байта
lodsb ; Если нам нужен байт (1 байт)
lodsw ; Если нам нужно слово (1 байт)
lodsd ; Если нам нужно двойное слово (2
; байта) 386+
- STOS: помещает в приемник значение позиции ES:[DI]
les di,al ; Нельзя это сделать!
les di,ax ; Нельзя это сделать!
stosb ; Если нужен байт (1 байт)
stosw ; Если нужно слово (1 байт)
stosd ; Если нужно двойное слово (2 байта)
; 386+
- CMPS: сравнивает значение в DS:[SI] со значением в ES:[DI]
cmp ds:[si],es:[di] ; Не может быть два переопределения
; сегмента!
cmpsb ; Если нужен байт (1 байт)
cmpsw ; Если нужно слово (1 байт)
cmpsd ; Если нужно двойное слово (2 байта)
; 386+
- SCAS: сравнивает значение приемника с ES:[DI]
¤ 16-ти битные регистры:
cmp ax,es:[di] ; 3 байта
scasb ; Если нужен байт (1 байт)
scasw ; Если нам нужно слово (1 байт)
scasd ; Если нам нужно двойное слово (2 байта)
; 386+
Обычно использование 16-ти битных регистров в плане оптимизации лучше, чем использование 8-ми битных. Давайте посмотрим пример с инструкцией MOVЖ
mov ah,06h ; 2 байта
mov al,66h ; 2 байта (вместе 4 байта)
mov ax,0666h ; 3 байта
Также увеличение/понижение значения 16-ти битного регистра тоже более выгодно:
inc al ; 2 байта
inc ax ; 1 байт
dec al ; 2 байта
dec ax ; 1 байт
¤ Базы и сегменты:
Перемещение из одного сегмента в другой нельзя сделать напрямую, поэтому мы должны сделать это одним из следующих способов:
mov es,ds ; Это сделать нельзя!
mov ax,ds ; 2 байта
mov es,ax ; 2 байта (вместе 4 байта)
push ds ; 1 байт
pop es ; 1 байт (в сумме 2 байта)
Использование DI/SI более выгодно, чем использование BP.
mov ax,ds:[bp] ; 4 байта
mov ax,ds:[si] ; 3 байта
¤ Процедуры:
Если вы используете какой-то код много раз, подумайте о создании процедуры. Это может оптимизировать ваш код. Тем не менее, неправильное использование процедур может привести к обратному результату: размер кода возрастет. Поэтому если вы хотите знать, поможет ли вам создание процедуры, используйте эту маленькую формулу:
X = [rout. size - (CALL size + RET size)] * number of calls - rout. size
CALL size + RET size означают 4 байта. X будем количеством байт, которое мы сэкономим. Давайте посмотрим на типичный пример, перемещение файлового указателя:
fpend: mov ax,4202h ; 3 bytes
fpmov: xor cx,cx ; 2 bytes
cwd ; 1 byte
int 21h ; 2 bytes
ret ; 1 byte
У нас есть 8 байт + размер CALL... 11 байт. Давайте посмотрим, соптимизирует ли это наш код:
X = [ 7 - ( 3 + 1 ) ] * 3 - 7
X = 2 байта сэкономлено
Это, конечно, надуманные вычисления. Вы можете вызывать эту процедуру больше 3-х раз (или меньше), что изменит размер, а также могут оказать свое влияние другие факторы. ¤ Последние советы по локальной оптимизации:
- Используйте SFT. В этой структуре множество полезной информации, и вы можете манипулировать ими безо всяких проблем.
- Пусть ваш компилятор делает по крайней мере 3 прохода, чтобы удалить все ненужные NOP'ы и другой отстой.
- Используйте стек.
- Также во многих случаях выгодно использовать инструкцию LEA.
[C] Billy Belcebu, пер. Aquila