Size Optimization (DeVilMan) .
Intro
In this tutorial we will see how to optimize code in size, making it smaller to fit your needs. These kinds of optimizations are often used by virusers and crackers that need to write routines as small as possible to be put into really small caves. Anyway these tricks can be obviouvsly applied to ''normal'' programming, so have a look at them and keep trying to make your code ever more smaller and faster! This is the true ASM programmer spirit.
Firstly, I apologize for my bad english, I hope you'll understand what I mean , at least looking at examples.
LET'S BEGIN , HaVe PHuN!
Zeroing a register
MOV register 0 is not a good way to do this. Instead use:
xor reg reg ; 1 byte.
or
mov reg reg ; 1 byte.
Use CDQ to zero out EDX if EAX is lower than 080000000h.
Remember that sometimes there's no need to clean a register before moving data into it. Consider his example:
xor eax eax ; 2 bytes.
mov ax W§esi+4 ; 4 bytes.
It can be optimized to:
movzx eax W§esi+4 ; 4 bytes.
, saving 2 bytes.
Testing for zero
Well, NEVER use:
CMP reg 0 ; 6 bytes.
jz L2> ; 2 bytes.
it's a waste of space. Instead use:
OR reg reg ; 2 bytes.
jz L2> ; 2 bytes.
I think it's worth the effort, you've halved its size!
Moving data between registers
XCHG is a powerful instruction. It EXCHANGES the contents of two registers, but it takes only 1 byte if one of them is the accumulator (EAX). So XCHG eax, ecx will take only 1 byte, being very useful in some situations.
For example:
MOV ebx eax
XOR eax eax
can be optimized to:
XCHG ebx eax
XOR eax eax
In fact it doesn't matter if you are putting the contents of ebx in eax, 'cause you're gonna clean it with the next instruction.
NOTE: XCHG takes less space than MOV , but it is slower and not pairable.
String instructions
String instructions (MOVS, SCAS, CMPS, STOS, LODS) can be very useful while dealing with strings. Even if they are pretty slow, they can be used to save a lot of bytes.
Have a look at these simple routines:
- find the end of an ASCIIZ string
mov edi StringAsciiZ
xor al al
L0: scasb | jnz L0<
- append two strings ( if you don't know their length)
mov edi StringAsciiZ
xor al al
L0: scasb | jnz L0<
mov esi SourceString
L1: lodsb
stosb | or al al | jnz L1<
-append two strings (if you know the length)
mov edi EndOfStringAsciiZ
mov esi SourceString
mov ecx D§SourceStringLenght
rep movsb
Pushing and Popping
A lot of Windows APIs require a lot of parameters which sometimes are optional or redundant, so you must push NULL many times. You can save some bytes in this way:
Instead of:
push &NULL ; 2 bytes.
push &NULL ; 2 bytes.
push &NULL ; 2 bytes.
call ...
use:
xor eax eax ; 2 bytes.
push eax ; 1 byte.
push eax ; 1 byte.
push eax ; 1 byte.
call ...
Also, if you are going to push and pop many registers
push ecx
push ebx
push edx
push esi
push edi
...
pop edi
pop esi
pop edx
pop ebx
pop ecx
mov eax D§esi
you'd better use PUSHAD and POPAD
PUSHAD
...
POPAD
mov eax D§esi
obviouvsly only if you don't mind to save the value of a particular register.
Checking for -1
There are some APIs that return -1 (0ffffffffh), often if an error is occurred. For example CreateFile returns -1 if it failed. A common and wasteful way to do this is:
call 'KERNEL32.CreateFileA' ...
CMP eax, 0_FFFF_FFFF ; 6 bytes
jz err_invalid_handle ; 2 bytes.
Think of it in this way:
call 'KERNEL32.CreateFileA' ...
inc eax ; 1 byte.
jz err_invalid_handle ; 2 bytes.
dec eax ; 1 byte.
in fact 0FFFFFFFF+1=0
Wiping the HIGHWORD
A nice way to do this is using a mask:
AND ecx, 0FFFF ; 6 bytes.
BUT , if ecx is lower than 08000h , you can halve the size using CWDE (Convert Word to Extended Doubleword), which extends the sign bit of AX throughout EAX.
XCHG eax ecx ; 1 byte.
CWDE ; 1 byte.
XCHG eax ecx ; 1 byte.
Nice, isn't it?
Words registers
Try to avoid the use of word registers , because 16-bit instructions executing in 32-bit mode require an operand-size prefix, which occupies 1 more additional byte.
Moving Immediate Values
MOV always takes a 4 byte immediate operand even if it's a byte sized one. This means that a
MOV eax 10 ; 5 bytes.
takes 5 bytes!
Instead, you can PUSH a byte sized immediate value in only 2 bytes:
PUSH 10 ; 2 bytes.
Thus you can save some bytes with a PUSH immediate/ POP reg combo.
PUSH 10 / pop EAX ; 3 bytes
Powerful IMUL instruction
IMUL can be used as to perform a multiplication by an immediate value
IMUL eax 30 ; 3 bytes
instead of using:
PUSH 30 ; 2 bytes.
POP ecx ; 1 byte.
MUL ecx ; 2 bytes
but also to perform a multiplication by an immediate and a move in only 3 bytes!
IMUL eax ecx 025 ; 3 bytes
which multiplies ecx by 025h an puts the result in eax ( ecx is not touched).
Random values
Instead of using a randomize() and then a random() functions, you can in some cases use RDTSC instruction ( Read Time-Stamp Counter), but remember it's a Pentium instruction. It loads the current value of the processor's time-stamp counter into the EDX:EAX registers. EAX, the lower 32 bits, is a kind of ''random'' value(it's NOT random at all, but it changes so fast that lower bits can be considered random).
So to do something , let's say, a time every 4 consider the following example:
RDTSC
cmp al 63
jnb over
; Do something
over:
Or if you want a pseudo-random value between 0 and 3 do the following:
RDTSC
shr eax 30 ; leaving only 2 bits, you've got a number between 0 and 3
NOTE: put a BSWAP EAX before shifting if you want a more random value...
LAST WORDS
I hope you've find these info useful :) Remember that in the PC world SMALL things are usually better than LARGE ones. (heh...not like real life, where lots of things SHOULD be as large as possible....).
DeVilMAn - 2001
< devilman@flymail.it >
EvIl GeNiuSes foR a BetTeR ToMoRrow
~~~~~~~