Integers in x86 Assembly ..
The Integer Sizes
The very first thing a beginner has to learn about Assembly Language is that no operation can be done without knowing and taking care of the size(s), with anything, and particularily not with Numbers.
As described in X86_Basics, the main Sizes are the Byte, the Word, the dWord (and qWord for some Instructions).
Also, the Processor does not care, at all, if you consider, say a Byte, as representing, for you, in your human representation, a Text Char, a Flag, an Integer, or whatever else. The only thing the Processor considers is that a Byte is a Byte. For Integers, let us first describe the behavior of the Processor when operating, for example, on a Byte: As you now, know, a Byte content may vary from 0 to 0FF (0 to 255). Lets set the Value of a Byte Register (same for a Memory Byte, of course), to 0:
> mov al 0
... then, let us subtract 1:
> sub al 1 ; same with dec al
... then, the al value will be 0FF. The reverse is also true:
> mov al 0FF | add al 1 ; same with inc al
... then, the al value will be 0. The value of the next coming Byte, ah, will not be modified, in any case. Same for a Memory Variable:
[MyVariable: D$ ?]
mov B$MyVariable 0FF | inc B$MyVariable | Hexprint D$MyVariable ; The Hexprint Routine will show '0'.
(This behavior is, of course, identical for any other Size than a Byte).
Take care of the Size Markers when reading the upper two Lines: I have declared 'MyVariable' as a dWord, and I have pointed to it as a Byte for the operations. Then, I view it back again as a dWord. Notice that, though 'MyVariable' is declared a dWord, applying to it a Byte operation can not modify its second Byte.
RosAsm being an Assembler (and not a Compiler), you are perfectly allowed to implement such ''unconventional' manipulations of the Sizes. HLL Typings (defining, once for all, that a given Variable is of a given Size and/or of a given quality) are utterly incompatible with any kind of Assembly. This may be a problem for beginners coming from HLLs, who may feel deprived of a useful security, but, by no way we could implement such hard limitations as Typings in an Assembler.
Accessing Bits
Bytes are the base unit of x86 Programming. Not Bits, as wrongly, but regularly stated. Accessing Bits requires performing Mask Operations. Examples:
Reading the lower bit of the second Byte of a dWord:
> mov ebx 00_101_10101010_00000000
> mov eax ebx
> and eax 00_1_00000000 ; eax = 0
> mov ebx 00_101_10101011_00000000
> mov eax ebx
> and eax 00_1_00000000 ; eax <> 0
Writing (setting on) the lower bit of the second Byte of a dWord:
> mov ebx 00_101_10101010_00000000
> or ebx 00_1_00000000 ; ebx = 00_101_10101011_00000000
Clearing (setting off) the lower bit of the second Byte of a dWord:
> mov ebx 00_101_10101011_00000000
> and ebx 00_11111111_11111111_11111110_11111111 ; ebx = 00_101_10101010_00000000
Reversing (whatever previous state) the lower bit of the second Byte of a dWord:
> mov ebx 00_101_10101011_00000000
> xor ebx 00_1_00000000 ; ebx = 00_101_10101010_00000000
Signed Integer
Though the Processor really offers several Instructions that do take care of Signs, for Integers (See for example, the S Flag in Flags_and_Jcc) the fact of considering an Integer as a Signed or as an unsigned Integer remains under your own interpretation choice, only. What makes the difference is only with considering the Higher Bit of a Byte, Word, dWord, as a Negative Sign marker, or not.
When having, say, 0FF Value in al, you may consider that the al Value is 255. Viewing it as a Signed Byte, you will read 0-1. This is to say that the Positive Value ranges, for signed Numbers, will be:
Byte: 0 to 00_01111111 ( = 07F = 127)
Word: 0 To 00_01111111_11111111 ( = 07FFF = 32,767)
dWords: 0 to 00_01111111_11111111_11111111_11111111 (07FFF_FFFF = 2,147,483,647)
... and the Negative Values ranges will be:
Byte: 0FF to 080 ( -1 to -128)
Word: 0FFFF To 08000 ( -1 to 32,767)
dWords: 0FFFF_FFFF to 08000_0000 (-1 to = -2,147,483,648)
(For each Size, the Binary of the maximum negative Value, is 00_10000000.....)
~~~~~~~
Numbers ..
Decimal Numbers
As several Base notations are used in programming, let us start with a review of what the Base is for Decimal Numbers. When we say that, in Decimal notation, the base is 10, we mean that the computation of a Number is based upon powers of 10, and that each Digit represents a value from 0 to 9. Example: 2,643 Decimal notation may be represented as follow:
(3*10^0) + (4*10^1) + (6*10^2) + (2*10^3)
First clearly understanding how we play with those powers of ten in Decimal Notation makes it much easier to understand HexaDecimal and Binary Notations, as they all may be expressed in the same manner.
Hexadecimal Numbers
Though it is popularly said that Computers are basically 'Binary Systems', the true base of Numbers computing is not at all the Bit (0 or 1). The lowest accessable level for Numbers, either in a Computers Registers or in Memory, is the single byte (8 bits) in Hexadecimal form. Accessing Bits is not the 'natural' way and requires extended manipulative logical efforts.
The Base for HexaDecimal is 16 and one 'Digit' is represented by a Byte, that is, a number ranging from 0 to 15. The smallest direct access to any Memory content or any register content is the Byte size. As we only have ten traditional Digits in Decimal Notation, HexaDecimal is represented by these 10 usual Digits, plus an additional range of letters, from 'A' to 'F':
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
If you see this for the first time, note, for example:
... that 0F + 1 = 010,
... that the maximum value in a Byte is 0FF,
... that if you add 1 to a 0FF Byte, its Value is zeroed,
...and that, in RosAsm syntax, what differentiates the various Notations are the leading zero Digits:
11 > 11 Decimal
011 > 11 Hexa (17 Decimal)
0011 > 11 Binary (3 Decimal)
02643 HexaDecimal notation may be represented as follow:
(3*16^0) + (4*16^1) + (6*16^2) + (2*16^3)
All of the usual Numbers Sizes units are based upon Hexadecimal. The commonly used sizes are:
Byte > 1 Byte > 8 Bits (0_11)
Word > 2 Bytes > 16 Bits (0_2211)
dWord > 4 Bytes > 32 Bits (0_4433_2211)
qWord > 8 Bytes > 64 Bits (0_8877_6655_4433_2211)
Binary Numbers
In the binary number system, based upon powers of 2, the Digits are either 0 or 1.
0_0110_1010 Binary notation may be represented as follow:
(0*2^0) + (1*2^1) + (0*2^2) + (1*2^3) + (0*2^4) + (1*2^5) + (1*2^6)
Accessing Bits, inside the real Hexadecimal of Memory or Registers requires particular indirect masking operations, that can be performed either by some Mnemonics or by masking the values 'by hand':
test eax 00_1000
and B§Memory (not 32)
Hexadecimal in Memory
Say we have an Hexadecimal dWord Value of 0_1122_3344 stored in a File. Then you search for this Value with any Hexadecimal Editor: You will not at first see it. Its real appearance in the dead File Bytes flow will be as this:
xx xx xx 44 33 22 11 xx xx xx
Why are the Byte parts of a dWord Number in the pseudo reverse order? Well, this may seem stupid, at first , but this its really not: Storing the Values this way has the great advantage of allowing the Values accesses with a certain consitency, no matter the accessed size:
Say the upper value is pointed by a symbol as 'Value'. Now, if you do:
mov al B$Value | mov bx W$Value | mov ecx D$Value
al = 044 // bx = 0_3344 // ecx = 0_1122_3344
This is to say that, if you have a qWord Number whose value is 1, stored somewhere, you also may read it either as dWord, Word or Byte, the returned Value will be 1 in all cases. Are you sure you would have preferred it the other way round?
~~~~~~~
Addressing ..
Addressing is the act of specifying a memory location (Address) for reading from or for writing to.
Immediate Addressing
This is simply writing a given value into a Register or in Memory. Examples:
mov edi , 040301C
Under modern OSes, this way for Addressing is not used, as the actual Addresses of a running Application (RVA) are not supposed to be known by the programmer. The Assemblers (and Linkers) do this job by computing the symbolic names encountered in the Sources. Example: the upper statement could be found in a disassembly; but, in the Source code, it could be:
mov edi MySymbol
Register Addressing
Is loading a Register with an Address value previously stored in another Register:
mov eax ebx
Indirect Addressing
Is reading/writing from/to a Memory cell:
[Value: 124, 642, 0, 33]
mov ecx , D$Value
Register Indirect Addressing
Is reading/writing from/to Memory through a Register.
On the 80386+ you may specify any general purpose 32 bit register when using the register indirect addressing mode. D$eax, D$ebx, D$ecx, D$edx, D$esi, and D$edi all provide addresses. The D$esp always refers to the Stack and D$ebp is usually reserved, too, for Stack pointing.
Note that you must use the 32 bit names of the registers. You cannot use the 16 bit names because the Addressing Memory space is the one of the 32 Bits Mode. The following examples show the legal forms Set:
mov eax D$eax
mov eax D$ebx
mov eax D$ecx
mov eax D$edx
mov eax D$esi
mov eax D$edi
mov eax D$ebp
mov eax D$esp
; Example with our upper Data: [Value: 124, 642, 0, 33]
mov esi Value
mov ecx D$esi ; ecx = 124
Indexed Register Indirect Addressing
Is reading/writing from/to Memory through a Register plus an Offset:
mov esi Value
mov ecx D$esi+4 ; ecx = 642
Base Indexed Addressing
Same as above, but instead of an immediate Offset, we stand one more Register:
; Example with our upper Data: [Value: 124, 642, 0, 33]
mov eax 4
mov ebx Value
mov ecx D$ebx+eax ; ecx = 642
All examples can be given with a Memory as first member and register as second one (this is to say, to write to Memory, instead of reading from Memory).
SIB Addressing (Effective address)
For addressing a Memory content, the x86 offers an organization of the target, described as Scaled Indexed addressing modes. This is to say the combination of a Base, an Index and a Displacement.
[disp + index*n]
[base + index*n]
[disp + base + index*n]
Base and Index are any of the X86 32 bit general purpose registers and n is the value 1, 2, 4 or 8.
When running the Instruction, the X86 computes the effective address by adding disp, base, and index*n together. Examples:
mov eax D$ebx+esi*4+8
mov eax D$Value+ebx+ebx*2
mov eax D$Value+esi*8
The full syntax (maximum instruction possibilities) for Memory addressing is:
Address = Base + (Index * Scale) + Displacement:
mov eax D$Value+eax*4+ebx
Where ''eax*4'' is allowed by the so called SIB (Scale / Index / Base ) encoding, very useful for addressing a table of Words (*2), of dWords (*4), of qWords (*8). (You just increase EAX to point to the next record).
The 'Scale' is: 2, 4, 8
The 'Index' is, here, eax
The 'Base' is, here, ebx
(Value is called 'Displacement').
As RosAsm syntax allows you to add an immediate to the given Displacement (here, Value), you could even write, for example:
mov eax D$Value+32+eax*4+ebx
Take special notice that this single Instruction performs, all at once, the multiplication of eax by 4, and two Additions (the third addition of Value+32 is done by the Assembler, at Compile time).
Base Index Scale Displacement
EAX EAX 1 None
EBX EBX
ECX ECX 2 8-Bits
EDX + EDX * +
ESP 3 16-Bits
EBP EBP
ESI ESI 4 32-Bits
EDI EDI
Table of the Effective Address Computation.
~~~~~~~
Jumping ..
Jumps can be either short or long. Long jumps are coded on dWord and short jumps are coded on Bytes. As jumps can be either up or down (forward / backward), they are stored as signed values. A short jump can therefore only be 127 octets down or 128 octets up.
A jump can be either direct or indirect. Direct jumps are the ones which are known by the Assembler at compile time. Example:
MyLabel:
; ...
; ...
; ...
jmp MyLabel
At compile time, the Assembler knows where 'MyLabel' will be located in Memory at run time. So, it can directly encode the desired immediate Address.
An indirect call is a call to some location that will be known only at run time. This is what we do, for example when calling for an api after loading a DLL 'by hand'.
Call D$SomeExternalFunction
Unconditional Jumps
This is the simple jmp instruction.
Conditional Jumps
They are all these 'j..' jmps following a comparison (cmp or test). The execution depends on the state of the Flags Register (see also Flags_and_Jcc):
Flags: O S Z P C
Simple Flags tests Instructions:
je / jz . . 1 . .
jne / jnz . . 0 . .
jno 0 . . . .
jnp / jpo . . . 0 .
jnz . 0 . . .
jo 1 . . . .
jp / jpe . . . 1 .
je . 1 . . .
Unsigned Math Instructions:
jb / jnae / jc . . . . 1
jbe / jna . . 1 . 1 (both ZF and CF set on)
jnb / jae / jn . . . . 0
jnbe / ja . . 0 . 0 (both ZF and CF set off)
Signed Math Instructions:
jl / jnge A B . . . (NOT (A=B))
jle / jng A B 1 . . (NOT (A=B)) OR ZF
jnl / jge A B . . . (A=B)
jnle / jg A B 0 . .
Read j as 'jump', e as 'equal', z as 'Empty', n as 'not', o as 'overflow', p as 'parity', a as 'above', c as 'carry', b as 'below', g as 'greater', l as 'lower'
cmp eax &FALSE | jne Error ; Jmp if not Equal.
Error:
Looping instructions
All loop Instructions rely on ecx value (they loop ecx times). All loop Instructions address Short jumps; This is to say that the included code, inside a loop cannot exceed 128 octets. For greater chunks of code, we have to use, for example, dec / cmp / jmp...
loop
Example, storing '0123456789' at MyString:
mov ecx 10
mov edi MyString
mov al '0'
L0: stosb | inc al | loop L0<
Beware that, if ecx value is zero before entering the loop, the chunk of code will be run 0FFFF_FFFF times (!!!!!) because The loop instruction first decrements ecx and only after doing so tests if ecx = 0.
To ensure the ecx is *not* zeroed before entering such a loop with a variable value, we have a particular instruction that does the same as ''cmp ecx 0 | je exit'': This is ''jecxz Exit''.
Loope / loopz
(2 mnemonics, 1 opcode)
Does the same job as loop, but stop looping if ZF if set on (1)
mov esi MyString | mov ecx 100
L0: lodsb
; ...
; ...
; ...
cmp al 0
loope L0< ; loops until ecx = 0 and while al = 0
Loopne / Loopnz
Same as above but with reverse added condition:
mov esi MyString | mov ecx 100
L0: lodsb
; ...
; ...
; ...
cmp al 0
loopne L0< ; loops until ecx = 0 and while al <> 0
Sub Routines Instructions
Call
Call is a jumping instruction that pushes the Address following the call onto the Stack before jumping. It is bound to:
Ret
... which pops the return Address and jumps back to it. ret may be followed by an immediate argument telling how many more bytes are to be stripped from the Stack. Useful for HLL organization of sub Routines with Parameters pushed onto the Stack before the call and the Stack being cleared by the callee.
ret 12 ; pops 3 dWords from the Stack and returns to caller.
~~~~~~~
Flags and Conditional Jumps ..
The Flags Register
The Flags Register is a particular Register that indicates the different states of CPU. Meaningful bits of this register are:
__| __| __| __| OF| DF| IF| TF| SF| ZF| __| AF| __| PF| __| CF|
where the meaning of set bits are:
OF > Overflow of math integer operation
DF > Direction Flag (set by STD)
IF > Interruption Flag
TF > Trap Flag
SF > Sign Flag (set by a negative result of integer Math operation)
ZF > Zero Flag (or Equal Flag)
AF > Auxiliary Carry Flag
PF > Parity Flag
CF > Carry Flag
Table Conditional Jumps
They are all these 'j..' jmps following a comparison (cmp or test). The execution depends on the state of the Flags Register:
Flags: O S Z P C
Simple Flags tests Instructions:
je / jz . . 1 . .
jne / jnz . . 0 . .
jno 0 . . . .
jnp / jpo . . . 0 .
jnz . 0 . . .
jo 1 . . . .
jp / jpe . . . 1 .
je . 1 . . .
Unsigned Math Instructions:
jb / jnae / jc . . . . 1
jbe / jna . . 1 . 1 (both ZF and CF set on)
jnb / jae / jn . . . . 0
jnbe / ja . . 0 . 0 (both ZF and CF set off)
Signed Math Instructions:
jl / jnge A B . . . (NOT (A=B))
jle / jng A B 1 . . (NOT (A=B)) OR ZF
jnl / jge A B . . . (A=B)
jnle / jg A B 0 . .
Read j as 'jump', e as 'equal', z as 'Empty', n as 'not', o as 'overflow, p as 'parity', a as 'above', c as 'carry', b as 'below', g as 'greater', l as 'lower'
~~~~~~~
The Stack ..
The Stack is a Memory area pointed to by the ESP Register (Stack Pointer).
In the early DOS times, the .com Files Format, for executables, was limited to 65,536 Bytes. This tiny room had to be used all at once, for Code, Data and Stack, so that, saving room, inside the executables was a high priority. Code and Data were then hosted downward from Byte 0100 and the Stack was organized backward (upward), from Byte 0FFFF (last Byte). The Code Part was coming usually first, then the Static Data, then, what we would call, nowadays, the Virtual Data, eventually growing down, while the Stack Data were eventually growing up.
This reversed organization of the Stack, due to these historical reasons, is still the one we have today. You may represent the Stack as a sock and the Stack Data as balls: Saving Data onto the Stack is like storing a ball into a sock, and retrieving Data from the Stack is like retrieving the first found ball on the sock top.
In the PE format, the Stack is provided to each uploaded Application by the OS. With RosAsm you can define the Minimum and Maximum Size of the Stack in the [File]/[Output] (if you understand what you are doing - there are few probabilities the default definitions would not fit your App requirements...-).
Pushing and Popping
Two x86 instructions are devoted to the Stack operations: PUSH and POP.
Pushing is storing (writing) some Data onto the Stack Top.
Popping is retrieving (reading) some data from the Stack Top.
Push and pop Instructions modify the esp Register Value by themselves. For the usual Operations, you are not supposed to modify esp by yourself, though this is also possible, for example, when creating the Procedure Stack Frame, as long as its initial Value is properly restored before exiting the Procedure.
The Stack must remain aligned on dWords boundaries (particularly under NT), so that, the natural size of Stack Data is the dWord. When you POP a dWord, ESP is decreased four bytes. Example with 'push esi', storing the actual value of esi on the Top of Stack:
__| __|
__| __|
__| __|
__| __|
__| __| <<< 012FEDC
__| __|
__| __|
__| __|
__| __| <<< 012FEE0
__| __|
__| __|
__| __|
(Before) (After)
Though the Stack must remain aligned on dWord boundaries, the Words operations are also allowed (there is no possibility for Bytes). Going on with the previous example, you may, as well, 'pop ax':
__| __|
__| __|
__| __|
__| __|
__| __| <<< 012FEDC
__| __|
__| __| <<< 012FEDE
__| __|
__| __|
__| __|
__| __|
__| __|
... and then 'pop bx':
__| __|
__| __|
__| __|
__| __|
__| __|
__| __|
__| __| <<< 012FEDE
__| __|
__| __| <<< 012FEE0
__| __|
__| __|
__| __|
... as long as, at the end, - and if you do not execute, for example, an Api call in between the two pops-, the Stack finally remains dWord aligned.
Calling and Returning
Two other Instructions make use of the Stack: CALL and RET.
When calling for a Routine, call is, internally, two operations: First, the Address of the next Instruction is pushed on the Stack. Second a jump to the targeted Routine is performed.
When the ret Instruction is executed, the return Address is popped from the Stack and the execution goes on with the first Instruction following the original call in the calling Routine.
Needless to say, any Stack fault in the calling Routine and/or in the called Routine will produce a hang. These hangs are most often due to unpaired push and pop statements. The absolute rule is that you must always pop what you have pushed, before processing any ret. A good practice, in order to prevent disastrous errors is to always indent you push and pop Instructions, and to avoid the Spaghetti_Style unless you perfectly know what you are doing.
The ebp Register
Ebp, (the Base Pointer), is usually reserved by Applications to keep track of a given Stack position, inside Procedures. For example, in a Disassembler, the way to point out the various Procedures in the Code Bytes Flow is to simply search all occurences of bytes [055, 08B, 0EC], which are the Code Bytes for the Instructions creating most Procedures Stack Frames initializations: push ebp // mov ebp esp.
During the Procedure life, ebp is then used as a fixed Pointer, keeping track of all transmitted Parameters (ebp+8, ebp+12, ebp+16, ...) and of the eventual private Data locations created by decreasing the Stack Pointer (ebp-4, ebp-8, ebp-12, ...).
Transmitting Parameters to Procedures
When you see, in most RosAsm Sources, Instructions like:
call MyProcedure Parameter1, Parameter2, Parameter3
the call is, in fact, a user defined Macro, unfolding this Statement into:
push Parameter3
push Parameter2
push Parameter1
call MyRoutine
In ''MyProcedure'', you have:
Proc MyProcedure:
Arguments @Parameter1, @Parameter2, @Parameter3
... Where @Parameter1 is equal to ebp+8, @Parameter2 equal to ebp+12, @Parameter3, equal to ebp+16.
Then at the End of the Procedure, you see usually the EndP Macro, which restores the Stack, if for example, Local Variable and/or Structure have been declared on the Stack, and that finally runs (going on with the previous example):
ret 12
Here, the Number following the ret, indicates that 3 dWords Parameters (12 Bytes) are to be erased from the Stack, so that the caller does not have to take any care of balancing the Stack, after the call, with as many:
pop Parameter1
pop Parameter2
pop Parameter3
Moving with Stack
As you may know, there is no such Instruction as:
mov D$Value1 D$Value2
in the x86 Instruction Set. For this operation, you have to write, for example:
mov eax D$Value2 | mov D$Value1 eax
if you fall short in Registers, you may, as well, write:
push D$Value2 | pop D$Value1
Neater with a simple Macro:
[Move | push #2 | pop #1]
move D$Value1 D$Value2
Splitting with Stack
On several occasions, the OS returns two Words Values in one single dWord. This is the case, for example, for the X and Y Positions of the Mouse Cursor, when your Application receives a &WM_COMMAND Message with a &WM_LBUTTONDOWN in wParam (the Mouse Position is in Lparam). Instead of masking, shr, and moving Instructions, you may simply state:
push D@Lparam | pop W$MousePosX | pop W$MousePosY
The Stack used for reversing
Because of the ''Pushed-first / Popped-last'' organization of the Stack, we can use it for reversing a flow of computed Data. Here is an example from the RosAsm Source.
When we have to write the Ascii form of a Number, inside a Chunk of Text, if we want to output ''one Space'' Separators, the fact of having to analyse the Number from lower to upper Nibbles and to output the String from left to right is a problem. The simpler way is to output, first, this String chars in the wrong order, and then to reverse it all. We can do this the easy way by pushing the Chars on the Stack, along with the Computations, and by popping them back to the Destination String Buffer. This way for reversing is not very efficient (the Stack operations are not very fast), but it is easy to understand and fast enough for small usual computings:
WriteEax:
mov ebx eax
If ebx = 0
mov B$edi '0' | inc edi | ret
End_If
; End Mark first:
push 0-1
; Select the Low-weight nibble:
L0: mov eax ebx | shr ebx 4 | and eax 0F
; Turn Ascii by use of a Translation Table (faster):
mov al B$HexaTable+eax
; Push the Char (in dWord al):
push eax
cmp ebx 0 | ja L0<
mov B$edi '0' | inc edi
; Now Pop and write to Destination set up to EDI by Caller:
L0: pop eax | cmp eax 0-1 | je L9>
mov B$edi al | inc edi | jmp L0<
L9: ret
Other Stack Instructions
Intel designed two more Instructions for use in HLLs: ENTER and LEAVE, that were proposed for advanced Stack Frames Management in Procedures, with nested access to ascendant Stack Frames, this is to say that a child Procedure could have access to its parent(s) Stack Frame(s). These Instructions have been very rarely used, even by Compilers. See the Descriptions for more info.
~~~~~~~
Registers ..
The CPU Registers may be represented as small Memory units directly located on the CPU. From this point of view, they are faster Memories than the real physical Memory. Also, several CPU Instructions make use of pre-defined Registers. In Win32 Assembly Applications programing, the commonly used Registers are:
GP (General Purpose) Registers:
eax, ax, ah, al
ebx, bx, bh, bl
ecx, cx, ch, cl
edx, dx, dh, dl
Strings Registers:
esi
edi
Stack Registers:
ebp
esp
(There are others of no interest in the very first steps, and of very little interest in Win32 Application writing).
The Flags Register is a particular Register that indicates the different states of CPU. More infos in Flags_and_Jcc.
You can write and read some values in all the Registers, according with their sizes: The first thing to know about a Register, before using it, is its size.
eax, ebx, ecx, edx, esi, edi, ebp, esp = dWord = 4 Bytes [0 > 4,294,967,295]
ax, bx, cx, dx = Words = 2 Bytes [0 > 65,535]
ah, al, bh, bl, ch, cl, dh, dl = 1 Byte [0 > 255]
ah, al are parts of ax. ax is part of eax; idem for b... , c... , d... Like this:
____________________
__| __| __| __| eax ; full 4 Bytes register
__| __| __| __| ax ; lower half 2 Bytes of eax
__| __| __| __| ah ; higher half of ax
__| __| __| __| al ; lower half of ax
This representation is easier to understand at a byte level. Reading from right to left, you have 4 Bytes in each Register. You can also view them as sets of 32 bits units, from Bit 0 to Bit 31.
AL is called the LOW byte of EAX and of AX
AH is called the HIGH Byte of AX.
AX is called the LOW word of eax.
To read the first BYTE in the EAX register (bits 0 to 7), and store its Value into a Memory Address, you may use, for example:
mov B$byteval al ; move 1st byte size value into the 'byteval' variable.
To read the second BYTE in the EAX register (bits 8 to 15)), and store its Value into a Memory Address, you may use, for example:
mov B$byteval ah ; move 2nd byte size value into the 'byteval' variable.
Same for the first WORD (bits 0 to 15):
mov W$wordval ax ; move 1st word size value into the 'wordval' variable.
To get at bits 16 to 31, you must rotate the bits in the register so they can be accessed by the previous instructions. Rotating a 32 bit register in either direction by 16 bits moves the LOW 16 bits into the HIGH 16 bits and the HIGH 16 bits into the low 16 bits of the register.
If you need to read and store, in a Memory Word, say, only the higher Word of EAX, you have to modify the content of EAX with the ROL or with the ROR Instructions:
rol eax 16 ; rotate EAX by 16 bits
or
ror eax 16 ; rotate EAX by 16 bits
... Then, the Higher Word is now in AX.
In all x86 Instructions, involving a Source and a Destination, the sizes of the two members must be the same. You cannot mix, for example, Bytes and dWords in one single operation:
mov eax cl ; Error: eax is 32 bit, cl is 8 bit
Though the upper rule does not apply on MOVSX and MOVZX Mnemonics, whose purpose is exactly with saving you from such situation:
movzx eax cl ; 'z' stands for 'zero' extended unsigned integer operation.
movsx eax cl ; 'S' stands for 'sign' extended signed integer operation
In some cases you may use,
mov eax 0 ; clear eax.
mov al cl ; copy cl into al.
Some less often used mnemonics also do this kind of conversion:
mov al cl ; copy cl into al
cbw ; convert BYTE in AL to WORD in AX
cwde ; convert WORD in AX to DWORD in EAX
Each Register may have some specific usage:
General Purpose Registers
eax, ebx, ecx, edx, are called 'General Purpose Registers', but they may have some specific task to assume under certain circumstances:
eax, 'Accumulator', is the most ''general purpose'' of all but it is slightly specialized for mathematical source and results.
ebx, 'Base', is often used for indexing.
ecx, 'Counter' is the required one for some counts.
edx, 'Data', is required for some integer Math operations
Offset Registers
esi and edi, Source and Destination Index, have specific usage in Strings_Instructions but can also be used for anything else you want.
ebp and esp, Base and Stack Pointers, are for Stack management. ebp can be used for what you want, but not esp. ebp is usually reserved for Stack pointing, in HLL style writing. esp is, most often, modified directly by several instructions (Call / Ret / Push / Pop ...). Out of the HLL macros for Stack frames managing, you should refrain from modifying esp or ebp.
~~~~~~~
X86_Basics ..
The Memory
X86 Assembly is a very simple and Flat Language. If you are used to Types, with some HLL Compiler, you will have to adapt to the fact that, in Assembly, the only corresponding concept is the one of the various available Sizes, as well for Memory accesses as for Registers.
The Basic Sizes of X86 Assembly are
Byte 8 Bits B$Address
Word 2 bytes W$Address
Double Word 4 bytes D$Address
Plus the New extensions ( MMX , XMM ):
quad word 8 bytes Q$Address // X$Address
As opposed to what you will read every now and then, the basic unit of memory is not the Bit, but the byte (packet of 8 Bits). You can not access an individual Bit, under X86, with any simple and direct operation. For reading / writing one single Bit, you have to make use of the Mask Operations as described in Logical .
I have several times been surprised to see that beginners may have some problems making up their mind with some mental representation of the Computer's Memory. This was made particularly clear, for example, with beginners' questions about how to work with Multi-Dimensional Arrays, in Assembly. So, it seems useful to say that:
The Computer's Memory is nothing but a linear ensemble of consecutive Bytes having an Address range between 0 and xxxx ('xxxx' depending on the amount of available installed Memory). Under modern OSes, the first available Address is not really zero, but, instead, the default Application upload Address -Usually 0400000 for an EXE PE, under ReactOS-.
Any Data stored in the Computer's Memory is nothing but a Number, and the only particularity of a given Data is nothing but its Size. For example, considering that some Data in Memory is either a signed Byte, or an unsigned dWord, or a Text String, it is always, and only a matter of interpretation, from the programmer's perspective, depending only on the usage he makes of this given Data.
More detail on Data Formats in Numbers.
The CPU
The Central Processing Unit is the hardware executing the set of Instructions of the machine Language. All these instructions are most often very simple, and may, or may not, require particular Registers and/or Memory accesses, provided in their expected Source and Destination Members.
~~~~~~~
Strategy Optimization ..
Size_Optimization and Speed_Optimization should be rejected from the Asm32 programming area. They are ridiculous, of no use in most cases, and hardly kill Readability, which is the most important thing to be preserved, in programming.
The superiority of Assembly over any HLL does not rely on Code Size and speed optimization, but on Strategy optimization and on a Specific Programming style (as opposed to a Modular Programming style). Yes, Strategy is particularly reserved to Assembly programming, for the very simple reason that HLL programmers cannot know what they are doing. Its as simple as that.
If giving many examples of Strategy Optimization is difficult, while giving counter examples is easy. You can see them everyday when running C Applications. Just take a look at the time Acrobat-Reader takes to find occurrences of one searched word, and when you will have finished laughing and drying your tears, ask yourself ''How is this possible???''. Well, the answer is that this requires sophisticated programming methods, which base is Modular Programming.
Modular Programming is the ensured way for producing fat and slow Code, because, to turn a Routine reusable, and flexible enough, you have to first make it able to hold all expected (and unexpected) possibilities. There are few reasons why an Assembly written Application should be faster than a C draft, if you use Modular Programing in Asm. Better to use Visual Basic. The worst aspect of Modular programming, is that, once a Module is available, you will continously be tempted to make a simple call to it, in any circumstance, no matter whether this way is accurate or not: It does the expected action. Period.
Good Asm Programing is re-inventing the wheel, or, at least, always reviewing how the wheel turns. This is good and desirable. Each time you re-invent a wheel, the wheel will be better. If you do not want to work again and again upon one given computing routine, just Copy/Paste from a previous work (with RosAsm, use the Clip feature, which is a friendly Source Level Librarian). Doing it this way will always give you full control of what you are really doing, when reusing and adapting some Template.
A frequently encountered example of Code reuse by Modular Programing is some Procedure for outputting a flow of Bytes under the form of Text Hexa. Inside RosAsm Source, I have to implement many those outputs. Each time I have to do it, I re-write, because there is no such a thing in the real world as a general purpose Hexa outputting: In one case, you may want leading zeros, in another one, you may want either Bytes or bigger formating. In one other case, the source format is known, whereas in another one it would not be... Is the outputted Text under Raw-aligned format, or not?... and so on... and so on... If such a basic and utterly simple concept as having a simple Module for outputting the simple Hexa Text form of an Number is 100% stupid and ineffective, what about more complex Modules ???...
Some examples of Strategy Optimization
Length of Text
Computing the length of short and middle length Texts does not require any optimization. Processors are fast enough. Applied upon really huge Data, this computation may slow down processes, from the final users point of view. In such cases, the answer is quite simple: No optimization trick-and-tip will ever make any difference for the human perception of time . The only way is to not run this computation: Example, when you load a text File in Memory, the Reading Function provides you with the Length of the File. Simply store this Value in a Variable, and increase / decrease it at each Data modification. This takes no time.
Sorting Tables
One example of a problem encountered was when developing the RosAsm Disassembler: When disassembling, each time an Instruction includes an evocation of some Code 'Symbol' (any Code Address, in fact), we have to store this pointer somewhere, in order to later write the targeted Label in the final Source output. In the very first version of the Disassembler, I simply stored those Pointers in a Table, and then sorted them in ascendant order between the two disassembling Passes. Result: Most of the time was then spent at sorting!!!
Solution: 'Fast Sort Algo???' > 'Ridiculous!!!'. After this first release, I declared a Check Table of the same length, in Bytes, as the code itself. Then each time a Code evocation was encountered, I have simply set to 1 the byte at the same displacement in the Check Table. This indice Table takes no time, neither at disassembling, nor at source building. Just a simple inside adjustment, and it is done. The total amount of time for a complete disassembly was then divided by 3.
Avoiding Tables
Generally speaking, Tables, used for wide ranges of possible events, makes programming easier, but also slower. Searching one given value inside a set of Data that can not be ordered is a heavy time cost. Long flow of Cases Selections (If and friends, or others...), are much more killing to write, but much faster to run, because, in most cases, if not all, the selection(s) Cases tend to organize in some form of Selections Trees by themselves. This is the way I wrote the RosAsm Encoder and Disassembler: Zero Tables for the Encoder, one Table for the Disassembler. Results: When some Assemblers take 10 seconds, RosAsm takes 1 second; when other Disassemblers spend 1 minute, RosAsm spends 1 second.
Exchanging Pointers
One regular example given for Strategy is the pointers exchanging. Let us suppose you have two tables (same sizes), and you want to move all the Data from Table1 into Table2. (Of course, both Tables are dynamically attributed, and so, we have two Pointers-Variables to them). Instead of moving all the data, you just have to:
push D§Table1Pointer
push D§Table2pointer
pop D§Table1Pointer
pop D§Table2Pointer
and it's done. Or better with a Macro:
[Exchange | push #1 | push #2 | pop #1 | pop #2]
Exchange D§Table1Pointer D§Table2pointer
Splitting Tables
Once the Data, that an Application has to parse, grows in size, searching for a particular Item inside the huge Table(s) may dramatically slow down the overall Application processes. This is why you will find here and there so many various fast search Algos. Another very simple way is to organize your Tables Data in some kind of 'Sub-Tables', which are to be built by applying to the Data one or several Check and split criterium.
Let us take an example with a huge Table, which is a flow of zero ended Names followed by a numeric Value. Your Application Parser has to return the Value according to a given Name.
Sorting the Table Names in alphabetic order will allow the Parser to exit the search as soon as a 'DestinationName' will be found 'bigger' than the 'SourceName'. This is to say that the overall searches will be made twice as fast, on average. But this trick only applies in particular cases, when the sort time is shorter than the usage time, and when the Search Exit does not have to run into an error case and stop the user actions.
A much better way is to implement one other Pointers Table to each new first Char into the Name Table. I mean another Table of Pointers (Check-Table), the first one pointing to the first 'a-leading' Name, the second one pointing to the first 'b-leading' Name, and so on. Then, your search Algo speed will be multiplied by 26 and, instead of having to first entirely sort all of your strings in alphabetic order (which may take some time...), you will just have to sort your Strings Table on the first Character of each Name (which is much faster), if half of the Table user does not consume more time than the sort does.
I used this method for several RosAsm Assembler Parsers, working upon Source symbols sorted by sizes, (alphabetically sorted or not in the 'Sub-Table' -depending on the comparison of time required for alphabetically parsing the Data and the overall usage time-).
CheckSums Search
When having to search, for example, for a Value corresponding to some KeyWord, the search of the KeyWords references, in a Table, may severely slow down an Application, if the List of KeyWords is huge. Instead of storing the KeyWords List in a Table, you may, first, build a CheckSums Table: A Table of the CheckSums of all the KeyWords, and build a corresponding Table for the Values. Then, you sort the CheckSums Table in ascending order. When sorting, at the same time you modify the position of a CheckSum, in the CheckSum Table, you modify also the corresponding Value position, in the Value Table (associated Sorts).
Once done, pointing out a Value corresponding to a Name becomes quite simple, and blazing fast: You compute the CheckSum of the Name, and, when you have this Checksum, you search for it, in the CheckSum Table... with a 2n Search. When the position of the CheckSum is pointed out, in the CheckSum Table, the Value is at the same displacement, in the Value Table. Really killing fast.
What is a 2n Search:
esi > End of Table // ecx = Size of Table // eax = Searched CheckSum
Value at esi < eax >>> shl ecx 1 | sub esi ecx
Value at esi > eax >>> shl ecx 1 | add esi ecx
Loop upper two operations until found or ecx = 0
Note: If the Table size can not be a power of two, when ecx = 0, you also have to add a linear search upward or downward until smaller, or bigger, or out of Boundaries. Also, if the Table size may be power of two, you have to pad the empty part with 0-1, of course.
What is a CheckSum:
Any Algo computing a Number from a flow of Bytes. For the CheckSum Searches, you have to carefully choose an Algo providing uniformly distributed CheckSums (avoiding, as far as possible, producing the same CheckSums for two different Names -the Computing must have a verification security for this - ).
End
As a general rule, for the Assembly programmer, the answer to speed problems is quite simple: One task is not fast enough??? Simple: Do not do it!!! There is zero relationship between Ticks saved at Code level, on one hand, and the final user perception of time, on the other hand. The guys who save Ticks by Code level optimization are putting a sheet of paper on the floor, then they put their two feet on the paper and say: 'Hey! Look! I am closer to the moon'. Usually, saving 20 or 25% of run time by Code Level Optimization is considered a great and exceptional performance achievement. At the final user point of view, the cases when 20 or 25% of usage time may make some real difference are very exceptional, and, usually, if 10 Seconds are too long... 8 Seconds will also be too long!
Nevertheless, there are some valuable Code Level optimizations tricks that do not kill readability, that are cheap and that may seriously increase the overall speed:
All Data should be aligned on their own Boundary. This means that Words should be aligned on Words, dWords on dWords, qWords on qWords. This is no cost. When bad alignements are encountered, the Processors requires two Memory accesses instead of one.
Shift Instructions should be preferred for all Multiplication / Division of integers by powers of 2. This is not more difficult to read, requires zero extra Registers (as opposed to mul / div), and the execution is really much faster. See Multiplying Without MUL and IMUL in Machine_and_Arithmetic_Idioms.
LEA is very interesting for computing at once several Integer Math operations. See INTEGER MULTIPLY Paragraph in Speed_Optimization Chapter.
~~~~~~~
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
~~~~~~~