C O D E O P T I M I Z A T I O N An Assembly Guide Written by D A R K M A N / V L A D ------------------------------------------------- Use the AL/AX register instead of other registers ------------------------------------------------- Sometimes it is more optimized to use the AL/AX register instead of the other registers, comparing with registers could be done like this: cmp bx,1234h ; Compare BX with 1234h (4 bytes) A more optimized way of doing this is: cmp ax,1234h ; Compare AX with 1234h (3 bytes) However this can ONLY be done when the AL/AX register DOESN'T hold an important value, but if the AL/AX register is used many times it COULD be more optimized to use it even if you have to PUSH it and then POP it afterwards. ---------------------------------------------- Use the Data Segment instead of other segments ---------------------------------------------- Moving a value from the memory to AX could be done like this: mov ax,es:[si] ; Move ES:[SI] to AX (3 bytes) A more optimized way of doing this is: mov ax,ds:[si] ; Move DS:[SI] to AX (2 bytes) ---------------- Clear a register ---------------- Clearing a register could be done like this: mov ax,00h ; Clear AX (3 bytes) A more optimized way of doing this is: sub ax,ax ; Clear AX (2 bytes) Or a equally optimized way of this is: xor ax,ax ; Clear AX (2 bytes) --------------------- Clear the DX register --------------------- Clearing the DX register could be done like this: mov dx,00h ; Clear DX (3 bytes) Or like this: xor dx,dx ; Clear DX (2 bytes) A more optimized way of doing this is: cwd ; Convert word to doubleword (1 byte) However this can ONLY be done if the AX register is less than 8000h. --------------------------- Test if a register is clear --------------------------- Testing if a register is clear could be done like this: cmp ax,00h ; AX = 0? (3 bytes) A more optimized way of doing this is: or ax,ax ; AX = 0? (2 bytes) ---------------------------------------------------- Use a 16 bit register instead of two 8 bit registers ---------------------------------------------------- Moving a value to a 16 bit register could be done like this: mov ah,12h ; Move 12h to AH (2 bytes) mov al,34h ; Move 34h to AL (2 bytes) A more optimized way of doing this is: mov ax,1234h ; Move 1234h to AX (3 bytes) However this can ONLY be done if the two 8 bit registers are the high and low register of the same 16 bit register. --------------------------------------------------------- Move the AL/AX register to another register or vice versa --------------------------------------------------------- Moving the AL/AX register to another register could be done like this: mov bx,ax ; Move AX to BX (2 bytes) A more optimized way of doing this is: xchg ax,bx ; Exchange AX with BX (1 byte) However this can ONLY be done if the source registers value is unimportant afterwards, because the source register will hold the destination registers value. ------------------------------- Use DI/SI as base instead of BP ------------------------------- Moving a value from the memory to AX could be done like this: mov ax,ds:[bp] ; Move DS:[BP] to AX (3 bytes) A more optimized way of doing this is: mov ax,ds:[si] ; Move DS:[SI] to AX (2 bytes) If the DI/SI index is used many times it COULD be more optimized to used the DI/SI index even if you have to PUSH it and then POP it afterwards. -------------------------------------------------------- Use the CMPS, LODS, MOVS, SCAS, STOS and REP instuctions -------------------------------------------------------- Moving a value from the memory to AX could be done like this: mov ax,ds:[si] ; Move DS:[SI] to AX (2 bytes) A more optimized way of doing this is: lodsw ; Load AX with DS:[DI] (1 bytes) Remember to clear/set the direction flag. Sometimes it COULD be more optimized to use these instructions even if you have to PUSH and then POP the used registers/segments afterwards. --------------------------- Move a segment to a segment --------------------------- Moving a segment to a segment is weird, because you can't move a segment to another segment directly by: mov ds,cs ; Can't do this! Because of that, you could use a register as a temporary storage, like this: mov ax,cs ; Move CS to AX (2 bytes) mov ds,ax ; Move AX to DS (2 bytes) But if an important value is held by the AX register, you have to PUSH and the POP it afterwards, that would add the code by 2 bytes, a more optimized way of doing this is: push cs ; Save CS at stack (1 byte) pop ds ; Load DS from stack (CS) (1 byte) ----------------------------------------------- Use SHL/SHR instead of the DIV/MUL instructions ----------------------------------------------- Multiplying AL with 2 could be done like this: mov bh,02h ; Move 02h to BH (2 bytes) mul bh ; Multiply AL with BL (2 bytes) A more optimized way of doing this is: shl al,01h ; Multiply AL with 02h (2 bytes) This can ONLY be used if the source can be divided by 2. ---------------------------------------- Use object codes instead of instructions ---------------------------------------- A far call could be done like this: call far address ; Make a far call (3 bytes) address dd ? ; Address of a procedure (4 bytes) A more optimized way of doing this is: callfar db 9ah ; Object code of a far call (1 byte) address dd ? ; Address of a procedure (4 bytes) This will ONLY optimize your code if the immediate value after the object code is a word or greater. -------------- Use procedures -------------- If some code is used many times and it's size is large, it COULD be more optimized to use it as a procedure, the following formula can calculate if it is more optimized to use a procedure: Bytes saved = (procedure size - 4) * number of invocations - procedure size Figure 4 in the parentheses of the formula is there because the size of the CALL and RET instructions together are 4 bytes. ------------------------ Make procedures flexible ------------------------ Make procedures flexible if possible, this CAN optimize your code, because the redundant code is removed, a example from this is: movefptrend proc near ; Move file pointer to the end mov al,02h ; " " " " " movefileptr proc near ; Move file pointer to end/beginning cwd ; Convert word to doubleword movefpointer proc near ; Move file pointer to a offset xor dx,dx ; Clear DX mov ah,42h ; Move file pointer int 21h ; Do it! ret ; Return! endp endp endp Use the formula from above to calculate if the flexible procedures can be used to optimize your code. ---------------------------------- Use all the information in the DTA ---------------------------------- DTA (Disk Transfer Area) is used by service 4eh and 4fh in the DOS functions interrupt (INT 21h), the contents of the DTA block are: ---------------------------------------- Offset Size Contents ---------------------------------------- 00 Byte Drive letter 01-0B Bytes Search template 0C-14 Bytes Reserved 15 Byte File attribute 16-17 Word File time 18-19 Word File date 1A-1D DWord File size 1E-3A Bytes ASCIIZ filename + extension ---------------------------------------- - If your want to reset the files time and date afterwards, then use the DTA instead of service 57h in INT 21h. - If you only want to infect one file, then change the drive letter to a illegal value instead of making redundant code in the exit part of the program, if you change the drive letter to a illegal value an error will occur. However, this will ONLY optimize your code, if you are already using the DTA. --------------------- Final tips and tricks --------------------- - Remove all unnecessary NOPs - Move your code around, to see if some JUMP NEAR instructions could be replaced by JUMP SHORT instructions etc. - Don't use instructions to calculate values that can be calculated directly in a parentheses. - Use LEA instead of MOV OFFSET. - Use the stack to store temporary data, but be careful if it is a COM file. - Use the CBW instructions to clear the AH register if AL is less than 80h. - Use DEC/INC instead of ADD/SUB register,01h. - When using DEC/INC it is more optimized to use 16 bits registers rather than 8 bit registers.