ASM Instructions - Reduced Set
MOV eax, 234
MOV ecx, eax
MOV cl, 0
MOV ax, word ptr [MyVariable]
The most used instruction!
It moves the value from right operand to left operand.
Note: in spite I'm using the word "move" in the reality it is only a "copying" from destination to source:
For example, if ECX register contains value 83, the instruction:
MOV eax, ecx
it will move the 83 value in eax and at end in both register there will be the same value (83)
Since the left operand will receive it will be always register or an memory address.
Anyway in the assembly we'll use we can't use the syntax:
mov dword ptr [$403000], ecx
to move the value from ecx to memory with address $403000 but we'll use or a mnemonic variable, when it has been defined in our source code:
// defined in C++ source side:
// while we'll use it in ASM section in following way:
mov dword ptr [BreastSize], ecx
or, when we have to type a value in some tomb4 address, we'll have to copy that address in a register in this way:
MOV eax, $403000 ;our address where type a value
MOV DWORD PTR [eax], ecx ;move value in ecx in $403000 address memory
MOVSX EAX, BX
MOVSX CX, AL
MOVSX EDX, byte ptr [MyVar]
MOVSX is a moving instruction like MOV (see above) but the "...SX" says to use SIGN eXtending when we move a value from a short register to a longer register. (see registers table)
For example since AX is 16 bits wide, while ECX is 32 bits wide, we have to say to assembly how filling the highest 16 bits of ECX not covered by AX.
When we use MOVSX we say to extend the Sign of AX in higher 16 bit of ECX.
This means that we know that in AX there was a signed number and you wish that the sign attribute remains also when the value has been moved in ECX:
MOVSX ecx, ax
You'll use MOVSX syntax with signed numbers and different wideness of source and destination operands
MOVZX ecx, ax
MOVZX eax, cl
MOVZX edx, word ptr [MyVariable]
MOVZX works like MOVSX with the difference that in this case we have a Zero eXtending.
We'll use MOVZX when the source is an unsigned value and we wish having Zero in higher side of destination operand.
ADD eax, 4
ADD word ptr [MyVariable], 32h
The ADD instruction perform an adding between two operands, letting the result in destination (left) operand.
ADD eax, 5
the value in eax register will be increased by 5
SUB edx, 4
SUB dword ptr [MyVar], ecx
The SUB instruction subtracts the right operand to the left operand.
sub edx, 5
the value in edx will be reduced by 5
CMP eax, 34h
CMP cl, 1
CMP word ptr [eax], 30
CMP performs a comparation between two operands, and then, in following instructions, it will be possible performing a conditional jumping in according with the result of previous CMP instruction. See also Jxx instructions.
You'll use CMP when you wish perform different zone of the code in according with some condition.
Example, if eax = 12 we can perform following CMP e conditional jumps
CMP eax, 10
CMP eax, 12
As you can see, the left operand will be compared with right operand, for this reason the condition:
CMP eax, 10
has as result "above" (or greater) and the jump
will be performed jumping to Label
Jxx instruction change its name in according with wich conditional bits you mean perform the conditional jump.
These bits will be set after a CMP instruction between two operands (see description of CMP instruction) but there are also other instructions those can change these status bits.
This is the list of main conditional jumping you can use:
Jump if Above (used after comparison between unsigned numbers)
Jump if Below (used after comparison between unsigned numbers)
Jump if Above or Equal (unsigned numbers)
Jump if Below or Equal (unsigned numbers)
jump if two numbers, after a CMP instruction, are equal, or if after the SUB, DEC, INC, AND, TEST, OR instructions the result is zero
jump if two numbers, after a CMP instruction, are not equal, or if after the SUB, DEC, INC, AND, TEST, OR instructions the result is different than zero
Jump if Equal
This instruction is a synonymous of JZ
Jump if Not Equal
This instruction is a synonymous of JNZ
Jump if Greater
When the first (left) operand is greater than second (right) operand, in a signed comparison
Jump if Less
When the first (left) operand is less than second (right) operand, in a signed comparison
Jump if Greater or Equal
When the first (left) operand is greater or equal than second (right) operand, in a signed comparison
Jump if Less or Equal
When the first (left) operand is less or Equal than second (right) operand, in a signed comparison
Jump if Carry is set (=1)
The carry should be used to verify if after an addition (ADD) or subtraction (SUB) there have been a carry.
Jump if Carry is Not set (=0)
Jump if Signed bit is set (=1)
The signed bit is the highest bit of a signed number. When S is set the number is negative.
Jump if Signed is Not set (=0)
When the siged is 0 the number is positive.
JMP perform in unconditional way a jumping in new code set in the label following JMP
go to the code where we place the label "LoopRooms"
Perform the portion of code at address of operand, a label or absolute address, and then the control came back to the next instruction after the call.
add ecx, eax
mov ecx, 4
mov eax, 10
; and now the ecx register will contain 14, because the above code "add ecx, eax"
; has been performed with the call instruction
mov eax, 0 ;and then perform this instruction
Remark: many subprocedure have a C++ function style, like it happens also in tomb raider engine, in this case the argument will be passed before performing the "call" pushing in the stack the value for the the arguments, and at end of the call in the EAX register could be a result
For example a C++ function requiring two arguments could be called in this way:
push 4 ;second argument
push 10 ;first argument
add esp, 08h ;remove from stack two arguments (see PUSH/POP instructions)
; and now in EAX could be the result of SumTwoNumbers() function
The RET instruction performs a Return to the instruction following the CALL instruction that called current code.
mov eax, 3
mov ecx, 2
cmp eax, 0
;then, in some side of our code there will be the MyCoolProcedure, like
add eax, ecx
;.... other code
retn ;this instruction move the code execution of instruction "cmp eax, 0" following the call MyCoolProcedure
Note: see also the CALL instruction
Remark: the instruction RETN, where the final "N" is for "Near", is the same of RET instruction, since we use flat 32 assembly. The speech should be different using segmented 16 bit assembly, but this topic is not interesting for us.
To understand the meaning of PUSH and POP instructions it is necessary introducing the concept of "stack".
The stack is a reserved memory used from the microprocessor to store temporary some values.
Our code may store value in the stack, too.
The access to the stack is not direct, like for common memory with specific address.
We can store a value in the stack using a PUSH instruction.
Above instruction will save (but we use the word "push", of course) the value 45000 in the stack
To exctract newly this value we'll have to use the opposite instruction:
now in the eax there will be the last value pushed in the stack with PUSH instruction. In our example eax will be = 45000
We can store more values in the stack and then extract them with many POP instruction but in these operations we have to follow some rules to avoid troubles.
First rule is that the push and correspoding pair of POP have to be always correctly nested.
For example, we'll use very ofter the push/pop instruction to save the content of some register to restore them in a second time.
;here we change the value of eax and ebx for our computes
mov ax, 16
mov bx, word ptr [FlagsOfRollingBall]
test bx, ax
;and now we restore previous value of eax ebx
Above code is correct, because the sorting of push instructions is the opposite of following pop instructions, this is "nested" meaning.
But if after "ignore:", the end part of above code, we had typed:
We'll have taken a mistake, because the values of eax and ebx, should be inverted.
Second rule is that we can't jumping out of nested push/pop pair with JMP, Jxx or RET instructions.
This means that, everytime we push something in the stack, then we have to remember to extract it with POP before letting out that zone of code.
A typical error is showed in following code:
;saving some registers
; perform some code
cmp eax, 3
In above code, when eax is above than 3 the code to restore register will be skipped (jumping to the "GoGreater" label) and this is bad. In this case when the "retn" instruction will be performed there will be a crash (exception) because the code will be performed in chaotic way.
You should remember that the stack is used from microprocessor also to store the return address after a call, and if you push or pop in uncorrect way the values in the stack, the control will go in bad zone of code because the MP will be no more able to remember the correct back address after a call.
Third rule is about the requirement to compensate the push instructions used to pass argument to some C++ function (procedure).
This rule is not so different than the second, but in this case we have another solution to get our target.
We can simply changing the pointer to the stack, "to say" to microprocessor that some values have been removed without using the usual "POP" instructions.
The stack pointer is a register named ESP (Extended Stack Pointer)
push ecx ; argument 3
push 901 ; argument 2
push esi ; argument 1
add esp, 12
The last instruction "add esp, 12" works like wheter 3 value (of 4 bytes for each) have been POPPED from the stack.
It's true the we might use the instructions:
to extract the three arguments, but since we are not interested to have those values, we can save time saying only "ignore the last three values that I popped in the stack"
Remember that the push operations have always size DWORD and so each PUSH operation is 4 bytes saved in the stack, for this reason we have to multyply by 4 the number of PUSH instruction to extract to get the value to use with "add ESP, ..." instruction.
PUSHAD and POPAD works lisk PUSH and POP (see description of PUSH/POP instructions) but in this case these instructions push or pop ALL DWORD registers into/from the stack.
This couple of istructions is useful when you save all (or almost all) the registers without typing a lot of nested PUSH and then POP instructions.
It could be useful use a couple of PUSHAD and POPAD before calling a C++ function to avoid that the C++ function changed some register in its computes.
PUSHAD ;save all registers
push 34 ; arg3
push ecx ; arg2
push esi ; arg1
add esp, 0ch ;0ch in hex = 12 in decimal, to remove arguments
POPAD ;restore all registers
in above code we are sure that all registers will have the same value they had before of PUSHAD instruction.
OR ax, 10h
OR ecx, 1
OR word ptr [MyVariable], 20h
OR instruction allows to set single bits in destination operand.
These operations on single bits should remember to us the "flags" or ocb codes to set in tomb raider objects.
Really there are many circustances where we'll use single bits to remember micro informations to use in a second moment.
When we set a single bit in value (like a flag), we use the OR instruction.
OR ax, 10h
we set the bit with value 10h (i.e. 16 in decimal) in ax register
OR word ptr [MyOcb], 1
we set the bit with value 1 in our variable MyOcb
Then we'll be able to check the presence of these bits using the TEST instruction, or remove them with the AND instruction.
AND ax, 0FEh
AND dl, 03h
AND word ptr [MyOcb], ~16
AND instruction performs an and operations between two operand.
With the AND instruction you change the destination operand with a mask in source operand, where in destination operand will remain bit "1" only when they were present in source AND in destination.
For example, if we have in eax register the value "2" and we perform the instruction
AND eax, 2
in eax will continue to remain the value "2", because it was present in source and destination operands.
While if we have in eax the value 6 (that is the sum of bit 2 + bit 4) and we perform the instruction
AND eax, 2
in eax now there will be 2 and not 6, because the "4" bit was missing in source.
Pratically we use AND to clear specific bits of a value, like flags to remove.
In this case we'll use in source (right) operand a mask, where there are all bits set to 1, execpting that we wish clear in destination operand.
For example if we wish clear the first bit of operand (with value 1), we'll use this instruction
AND al, 254
254 is all bit of a byte (255 value), less the bit 1
Anyway there is a nice trick you can use to simply the compute of the mask to use.
If you use the "~" sign in front of a number, you create the mask for that number, i.e. the mask to clear that bit in destination operand.
For example we can clear the bit with value 4 in this way:
AND word ptr [MyOcb], ~4
and now the bit with value 4 will be zero in MyOcb, while all other bits will keep same previous value.
TEST al, 01h
TEST cx, dx
TEST instructions perform a sort of AND between two operand, but differently by AND, no operand will be really changed.
Tne only effect of TEST instruction is to set the status bits you can use for conditional jumps Jxx.
Pratically we'll use TEST to verify if some single bit of a value is set (1) or clear (0).
We know the OCB of object in tomb raider, and other flags in new trng features. Well, with TEST instruction we can discover if in some OCB value there is, a specific flag enabled or less.
For example if we wish know if in the [MyOcbValue] is set the flag 32 ($20 in hex) we can use these istructions:
TEST word ptr [MyOcbValue], 32
When the the result is Not Zero (jnz as jump instruction) the bit is present, while if the result is Zero (jz as jump instruction) the bit is missing
SAL ax, 3
SHL word ptr [MyVariable], 1
SAL and SHL perform a shift to left of bits in the operand.
This operation correponding to multyply the value by 2^x where x is the value of second (right) operand.
For example if eax = 4 the instruction
SHL eax, 3
it's like multiply eax by 8 (2^3 = 8) and for this reason now eax will be = 32
Theorically the difference between SHL and SAL is that the SHL works with unsigned numbers while SAL with signed numbers.
In the reality, in this case, with a left shift, there is no difference between the two instructions. The case is different for the opposite SAR and SHR instructions.
The left shift will be used to have a fast mutliply or to move a mask bit to perform then an AND or TEST instruction.
SHR ax, 4
SAR edx, 1
SHR and SAR instructions perform a right shift of bits of destination operand for source operand times. It corresponds to dividing by 2^x the destination operand, where the "x" is the value in source operand.
For example the instruction:
SAR edx, 4
it corresponds to dividing by 16 (2^4=16) the value in edx.
The SAR is a Shift Arithmetic Right, and it works with signed numbers, while the SHR works with unsigned numbers.
The difference is important because if you use a SHR (unsigned shift) on a signed number, you'll lose the sign attribute and the number will become positive, with a meaningless result.
XOR al, 07h
XOR eax, eax
XOR bx, cx
XOR performs an eXlusive OR, an operation on binary nature of the values.
The xor set 1 when the two corresponding bits of operands are different, while set 0 when the two corresponding bits have same value.
Anyway the above target is not very used.
You'll find a XOR instruction more often as fast trick to clear (set to 0) a register, using this syntax:
XOR eax, eax
In fact, using the same register for source and target operand, we'll be sure that after this instruction eax will be = 0
It's curious that this trick was used more often than the specific instruction CLR (CLear Register) born own to set 0 in a register but the reason is that the xor trick is faster to be performed than CLR instruction.
NEG word ptr [MyVariable]
The NEG instruction invert the sign of the value in the operand.
For example if the ax register contain 12 the instruction:
will transform the value in -12
LEA ebx, [eax*2]+3
LEA ecx, [edx*8][ecx+24]
LEA eax, [esi-3][ebx*4]
LEA esi, MyVector[eax*2]
LEA instruction is for Load Effective Address and it computes the result of address in right (source) operand like it was a formula.
To understand the usefulness of this instruction we can suppose that is useful converting a complicated address in a single register to have a more easy way to access many times this address, saving also the usage of other registers used to compute the formula.
For example, if we had to locate the begin of a record in a complex structure we could use formula like this:
then we wish typing different values in this address and those immediatly followings, we discover thas is more expensive in time terms (and typing terms) keep always in above form the address.
mov word ptr BaseVector+4[eax][esi*8], 1
mov word ptr BaseVector+6[eax][esi*8], 2
mov word ptr BaseVector+8[eax][esi*8], 3
please note the +4, +6, +8 to write the three values (1,2,3) in three next contiguous words.
While using the LEA method we'll have
lea eax, BaseVector+4[eax][esi*8]
mov word ptr [eax], 1
mov word ptr [eax+2], 2
mov word ptr [eax+4], 3
with the further advantage then after the LEA instruction we can use the ESI register as we wish because it will be no more used to keep the position of the address where we wish write.
Note: very often in tomb4 code you'll find these lea instructions to compute a very complicated address, in these cases the lea instructions, combined with addressing skills (see below) and SHL instruction, will be used to replace a multiplication.
This happens because the single MUL (multiplication) instruction is very slower than a list of LEA or SHL instructions and therefor it will be used a list of LEA, SHL both than a single MUL instruction.
For example in tomb4 code to multiply the index of a moveable by the size of a single item record (5622 or $15f6 in hex) will be used this list of instructions:
movsx eax, bx ; in bx there is the index to mutliply by 5622
lea ecx, [eax][eax*2]
lea ecx, [eax][ecx*4]
lea edx, [ecx][ecx*8]
lea ecx, [eax][edx*8]
mov edx, dword ptr [pVetItems] ;in pVetItems there is the base of items
lea ecx, [ecx][ecx*2]
lea esi, [edx][ecx*2]
well, now in esi there is the address with the structure of item with index bx.
But we will use, in easier way, the MUL instruction, of course.
In this way:
movzx eax, bx
imul eax, 5622
mov ecx , dword ptr [pVetItems]
lea esi, [ecx][eax]
and in esi we'll have the same address.
MUL edx, 2034
IMUL eax, 23
The MUL and IMUL perform a multiply.
The IMUL is for signed numbers, while MUL for unsigned numbers.
Theorically there are different way to use the MUL instruction in implicit way or explicit, with BYTE, WORD or DWORD scope, anyway to avoid futile complications I suggest to use these instruction always following this syntax:
MUL Register32, Number
MUL Register32, Register32
Where register32 is for extended registers: eax, ebx, ecx, edx, esi, edi
MUL eax, 3000 ;mutiply the value in eax by 3000, the result will be in eax
MUL ecx, ebx ;multuply the value in ecx by the value in ebx
Note: if the result is greater than 32 bits value, the higher side will be put in edx register.
DIV DWORD PTR [MyDivisor]
The DIV and IDIV instructions divide a number in the register eax with the divisor supplied as argument.
There are different way to work in according with the size of divisor but I suggest to use always a 32bit divisor to have always same working mode.
To divide a number you have to move the value in eax, then if you use a 32bit divisor, you have to use CDQ instruction to extend eax in edx register, get in this way the quadword used as dividend. Then you'll type in explicit way the divisor. The quozient will be in eax while in edx you'll find the remainder.
;we wish dividing the value in ecx register by 1000
mov eax, ecx
cdq ;warning: this clears the edx register
now in eax we'll have the result (quotient), while in edx the further remainder.
The difference between DIV and IDIV is that IDIV is for signed numbers while DIV for unsigned numbers.
DEC word ptr [MyVar]
The DEC instruction decrements by 1 the value in the given operand.
the value in ecx will decremented by 1
Note: this instruction set the further Z and S bit status, in according with the final value of the value.
Therefor, if the after the DEC instruction the value become zero, the Z bit status will be set 1 and you can check this condition with a JZ or JNZ conditional jump.
mov ecx, 5
mov byte ptr [MyArray][ecx], 0
in this case the loop will continue until ecx becomes zero
using the conditional jump:
the loop will continue until the ecx value is positive
IND DWORD PTR [MyCounter]
The INC instruction add 1 to value of the operand.
See also the DEC instruction.