Optymalizacja programow pod katem wielkosci kodu
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.Zerowanie rejestrow
2.Przenoszenie do rejestrow 32bitowych wartosci z zakresu 0-255
3.Sprawdzanie wartosci zwracanych przez funkcje API
4.Wymienianie wartosci rejestrow

Programisci piszacy w asemblerze mysla, ze jesli pisze sie w asemblerze ich
kod jest juz max zoptymalizowany(w koncu to asembler! ;), ale jak sie przekonalem
wiele drog wiedzie do tego samego celu.

1.Zerowanie rejestrow
~~~~~~~~~~~~~~~~~~~~~

a) mov eax,0	5 bajtow	; B0,00,00,00,00
b) xor eax,eax	2 bajty		; 33,C0
c) sub eax,eax	2 bajty		; 2B,C0
d) and eax,0	3 bajty		; 83,E0,00

jak sie okazuje nawet najprostsza operacja moze zajac nawet 5 bajtow, ale
jesli zastosujemy np. xor-a ta sama operacja zajmie 2 bajty w wynikowym kodzie
programu.Czesto wykorzystuje sie wartosc 0 jako paremtr dla jakichs tam
funkcji api

a) standardowe rozwiazanie

push	offset szSansSerif	; lpFace			; 5bajtow
push	0			; pitch and family		; 2b
push	0			; output quality		; 2b
push	0			; clipping precision		; 2b
push	0			; output precision		; 2b
push	1			; char set identifier		; 2b
push	0			; strikeout attribute flag	; 2b
push	1			; underline attribute flag	; 2b
push	0			; italic attribute flag		; 2b
push	400			; font weight(normal)		; 5b
push	0			; base-line orientation angle	; 2b
push	0			; angle of escapement		; 2b
push	0			; logical average character	; 2b
push	0Dh			; logical height of font	; 2b
call	CreateFontA

laczna ilosc bajtow instrukcji potrzebnych do zapamietania paramertow 
wywolania procki CreateFontA zajmie w tym przypadku 34 bajty

b) zoptymalizownae rozwiazanie

sub	eax,eax							; 2b
push	offset szSansSerif	; lpFace			; 5b
push	eax			; pitch and family		; 1b
push	eax			; output quality		; 1b
push	eax			; clipping precision		; 1b
push	eax			; output precision		; 1b
push	1			; char set identifier 		; 2b
push	eax			; strikeout attribute flag	; 1b
push	1			; underline attribute flag	; 2b
push	eax			; italic attribute flag		; 1b
push	400			; font weight(normal)		; 5b
push	eax			; base-line orientation angle	; 1b
push	eax			; angle of escapement		; 1b
push	eax			; logical average character	; 1b
push	0Dh			; logical height of font	; 2b
call	CreateFontA

tym razem 27bajtow, w sumie maly zysk w porownaniu z poprzednia procedura
ale czasami te pare bajtow moze sie przydac do czegos innego.

c) serie

Gdy w "serii" musimy zapamietac na stosie zera zamiast

push	0		; 2bajty
push	0		; 2bajty
push	0		; 2bajty
push	0		; 2bajty
push	0		; 2bajty
push	0		; 2bajty
push	0		; 2bajty
================================
			+ 14bajtow
oraz

sub	eax,eax		; 2bajty
push	eax		; 1bajt
push	eax		; 1bajt
push	eax		; 1bajt
push	eax		; 1bajt
push	eax		; 1bajt
push	eax		; 1bajt
push	eax		; 1bajt
===============================
			+ 9bajtow

mozna wykonac to tak

sub	eax,eax		; 2bajty
push	7		; 2bajty
pop	ecx		; 1bajt
@save_args:
push	eax		; 1bajt
loop	@save_args	; 2bajty
================================
			+ 8bajtow


d) jesli zamierzamy wyzerowac rejestr edx normalnie robimy to przez np xor edx,edx
ale mozna zrobic to jeszcze prosciej korzystajac z instrukcji cdq(ConvertDoubletoQuad)
Instrukcja cdq powoduje,ze rejestr edx jest wypelniany bitem znaku z eax(31bit) wiec jesli
wiemy ze w eax mamy np 1 to wykonanie instrukcji cdq spwoduje wyzerowanie edx,nalezy
tylko uwazac stosujac cdq, czy bit znaku w eax jest ustawiony czy nie,bo jesli w eax
mielibysmy liczbe np

eax=80000001h=10000000000000000000000000000001b
              ^bit znaku

to wykonanie cdq spowoduje,ze edx zostanie zapelniony bitem znaku eax czyli 1,
wiec w edx znajdzie sie 0FFFFFFFFh.Instrukcja cdq zajmuje tylko jeden bajt.



2.Przenoszenie do rejestrow 32bitowych wartosci z zakresu 0-255
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

a) mov	eax,7Fh	5 bajtow	; B0,FF,00,00,00
b) sub	eax,eax	4 bajty		; 2B,C0
   mov	al,7Fh			; B0,FF
c) push	7Fh	3 bajty		; 6A,FF
   pop	eax			; 58

Czesto potrzebne jest przeniesienie wartosci z zakresu od 0-FF do rejestru 32bitowego
Rejestr 32bitowy przyjmuje dane w porcjach 32bitowych(no chyba stad ta nazwa ;)
a wiec jesli wykorzystamy w kodzie programu cos takiego

mov	eax,4

instrukcja zajmie 5 bajtow	; B0,04,00,00,00
4-ka jest traktowana jako 32bitowa wartosc gdy korzystamy z 32bitowych rejestrow.
Najbardziej zoptymalizowanym rozwiazaniem wydaje sie byc wykorzystanie stosu do
prznoszenia wartosci 0-127 do 32bitowych rejestrow

push	4			; 6A,04
pop	eax			; 58

wow 3 bajty, mimo, ze zajmuje to wiecej miejsca w notepadzie po skompilowaniu kodu
zajmuje mniej bajtow na dysku!Nalezy w tym miejscu wspomniec,ze kompilator zapisze
skrocona forme push-a jesli wartosc bedzie miescic sie w granicach 0-127,jesli przekroczymy
ta wartosc i wymusimy na kompilatorze zapisanie skroconej formy push-a dla dowolnej
wartosci bajtu np definiujac makro:

pushb	macro	byteval
	db	06Ah,byteval
	endm


pushb	080h	; zapamietaj na stos 128
pop	eax

po wykonaniu tych instrukcji w eax znajdzie sie wartosc 0FFFFFF80h(-80h)ale dlaczego nie
00000080h?Heh liczby z zakresu 128-255 traktowane sa jako liczby ujemne ,jesli
uzyjemny skroconej formy push-a dla takiej liczby,na pozostale miejsca dword-a
zostanie skopiowany znak bajtu(1 binarna oznacza minus,0 plus)czyli w wypadku
wartosci 80h(128dec) bit 1 i tak zostanie zapisany dword na stosie jako rozszerzony
o znak

00000000000000000000000010000000 = 00000080h
			^bit znaku
11111111111111111111111110000000 = FFFFFF80h
^^^^^^^^^^^^^^^^^^^^^^^^bity oznaczajace znak

heh, zycie jest jak pudelko czekoladek...;).
Ale jest jeszcze inne rozwiazanie,zamiast "marnowac" 1 bajt uzywajac np

mov	eax,255	; 5bajtow

mozna w zastepstwie tego uzyc

xor	eax,eax	; 2bajty
mov	al,255	; 2bajty


3.Sprawdzanie wartosci zwracanych przez funkcje API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

W czasach VC++ i delphy chyba nikt juz nie przejmuje sie wartosciami zwracanymi przez
funkcje api, a czesto wlasnie sprawdzanie wartosci moze skrocic czas debugowania
programu.Ok ale to nie na temat, tak wiec funkcje jak to funkcje zwracaja jakies wartosci,
w przypadku funkcji WinAPI zwracana wartosc zawsze znajduje sie w rejestrze eax.W zaleznosci
od funkcji w eax zwracane sa wartosci np 0,-1,uchwyt pliku itp.Np funkcja CreateFileA
zwraca w eax wartosc -1 gdy nie mamy dostepu do pliku, ktory akurat chcielismy otworzyc.
Ale juz np.funkcja CreateIcon zwraca w eax 0 jesli wystapil blad.W kazdym razie bez winapi.hlp
programista piszacy pod win nie ma latwego zycia ;).Jak sprawdzic czy w eax jakas funkcja
nie zwrocila przypadkiem wartosci informujacej o tym ze wystapil jakis blad

push	...
call	LoadBitmapA

w helpie czytamy, ze

"If the function succeeds, the return value is the handle of the specified bitmap.
If the function fails, the return value is NULL. "

push	..
call	LoadBitmapA
cmp	eax,0		; 83,F0,00
jz	@wystapil_blad

ale chwilka instrukcja cmp eax,0 zajmuje 3 bajty.Czy nie da sie tego zrobic troszke
inaczej?Zamiast cmp mozemy zastosowac operacje logiczne takie jak or i test

call	LoadBitmapA
or	eax,eax		; 0B,C0
jz	@wystapil_blad

lub

call	LoadBitmapA
test	eax,eax		; 85,C0
jz	@wystapil_blad

obie instrukcje ustawia nam flage zerowa jesli eax==0 a wiec daja one taki sam
efekt jak cmp eax,0 ale zajmuja o 1 bajt mniej.Mozna jeszcze ten kod zoptymalizowac

call	LoadBitmapA
xchg	eax,ecx		; 1bajt
jecxz	@wystapil_blad	; instrukcja jecxz zajmuje 2 bajty(tyle samo co skoki jxx short)

Nalezy uwazac korzystajac z jecxz poniewaz jest to skok warunkowy o zakresie -127 do 128
i jezeli przekroczymy ten zakres przy wlaczonej dyrektywie jumps tasm przetlumaczy jecxz na

call	LoadBitmapA
xchg	eax,ecx
jecxz	@dummy
jmp	@next
@dummy:
jmp	@wystapil_blad
@next:

Funkcje api czasami jako kod bledu zwracaja w eax wartosc -1(0FFFFFFFFh), jak zatem
sprawdzic czy taka wlasnie wartosc zwrocila jakas funkcja np.CreateFileA?Najprostszy
rozwiazaniem jest oczywiscie

call	CreateFileA
cmp	eax,-1		; 83,F0,00
je	@wystapil_blad

a czy nie ten sam efekt da

call	CreateFileA
inc	eax		; jesli w eax bylaby wartosc -1 po zwiekszeniu o 1 zostalaby ustawiona
je	@wystapil_blad	; flaga zerowa
dec			; jesli nie wystapil blad zmniejszamy wartosc w eax o 1 tak aby 				
			; przywrocic oryginalna wartosc zwrocona przez funkcje

w tym przypadku kod wynikowy bedzie o 1bajt mniejszy

4.Wymienianie wartosci rejestrow
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Np mamy w eax 4 w edx 98 i chcemy, zeby w eax znalazla sie liczba 98 a w edx 4 czyli
zeby rejestry zamienily sie wartosciami.Jak to zrobic

push	eax
push	edx
pop	eax
pop	edx

rozwiazanie to zajmuje 4 bajty,mozna jeszcze tak

mov	ebx,eax
mov	eax,edx
mov	edx,ebx

"jedynie" 6 bajtow.Ale istnieje instrukcja xchg(eXCHange - wymien,zamien)
ktorej rozmiar wynosi 1 bajt jesli jednym z rejestrow ktorych wartosci wymieniamy
jest rejestr eax np

xchg	eax,edx		; 92h
ale juz
xchg	edx,esi		; 87h,0D6h

Jak sie okazuje wiele instrukcji jest zoptymalizowane pod wzgledem wielkosci jesli
rejestrem na ktorym operujemy jest eax np.

add	edi,400000h	; 81h,0C7h,00h,00h,40h,00h 6bajtow
add	eax,400000h	; 05h,00h,00h,40h,00h 5bajtow

jak widac instrukcja korzystajaca z edi ma rozmiar o 1 bajt wiekszy niz ta sama
instrukcja korzystajaca z eax

Ostatnie slowo
~~~~~~~~~~~~~~

Moze zdawac sie wam ze na cholere ta cala optymalizacja, ale jesli przypadkiem zamiast
nopowania jakiegos fragmentu kodu bedziecie potrzebowac jeszcze jakichs dodatkowych instrukcji
a przestrzen do wykorzystania bedzie bardzo skromna, wiedza o optymalizacji moze sie
baaaardzo przydac.


bart^CrackPl
cryogen@free.net.pl




