Saturday, March 9, 2013

Conditionals In Assembly

if(var >= 1)
{
printf("Hello!");
}
else if(var <= -1)
{
printf("Goodbye!");
}
else
{
printf("Error");
}

Anyone with programming experience(i.e. everyone reading this) knows what this code is and how it works.  How does it work in assembly, though?

(these are not full functions)
ARM:

cmp r0, #0
ldrgt r0, =#ptr_hello
ldrlt r0, =#ptr_goodbye
ldreq r0, =#ptr_error
bl printf
ldmfd sp!, {lr}
bx lr 


THUMB:

cmp r0, #0
bgt greater
blt less
b   zero

greater:
ldr r0, =#ptr_hello
bl printf
b end

less:
ldr r0, =#ptr_goodbye
bl printf
b end

zero:
ldr r0, =#ptr_error
bl printf

end:

pop {lr} 
bx lr


Big difference, huh?

Note how I'm using conditional suffixes on non-branch instructions in ARM making it very compact, whereas in THUMB you would only be using the conditionals to jump around in the code and run different code "banks" as I like to call them. This is a list of applicable conditionals:

eq  Z=1           Zero (EQual to 0)
ne  Z=0           Not zero (Not Equal to 0)
cs  C=1           Carry Set / unsigned Higher or Same  
cc  C=0           Carry Clear / unsigned Lower         
mi  N=1           Negative (MInus)
pl  N=0           Positive or zero (PLus)
vs  V=1           Signed overflow (oVerflow Set)
vc  V=0           No signed overflow (oVerflow Clear)
hi  C=1 & Z=0     Unsigned HIgher                              
ls  C=0 | Z=1     Unsigned Lower or Same                       
ge  N=V           Signed Greater or Equal                      
lt  N != V        Signed Less Than                             
gt  Z=0 & N=V     Signed Greater Than                          
le  Z=1 | N != V  Signed Less or Equal                         
al  -             ALways (default)
nv  -             NeVer 

The conditions that are being checked are based on the condition flags on the CPU.  A quick look at those:

Z:    zero- result was zero
C:    carry- (unsigned) result overflowed the destination register
N:    negative- result was negative
V:    overflow- same as C, but for signed math. 
 
  And here's an example of them in IDA:

 

Let's very briefly check out an example or two:

ex. 1:
ldr  r2, =0xFFFFFFFF
mov  r3, #0x1
adds r0, r2, r3

What does this set? 0xFFFFFFF is the max number for a 32-bit register, but our result is 0x100000000.  So the top bit is lost due to the size restriction and the result is 0x00000000.  So the flags are set as follows:

Z: The result was zero, so this is set
C: We lost some of the info in the final result due to the size, so the carry would be set
N: Zero is non-negative, so this would not be set
V: We're using two's compliment negatives here, so 0xFFFFFFFF is -1.  So for this, the math would be -1 + 1 which is 0.  Nothing overflowed here, so this is not set.

ex. 2:
mov r4, #0

loop:
/* do stuff here */
add r4, r4, #1
cmp r4, #10
blt loop

What is set with this code and compare?  r4 goes from 0-9(while it's true) which sets the flags as such:

Z: Will be set once r4 is 10 and break the loop.  Otherwise unset.
C: The carry is set if register1 is >= register2.  It will be set on r4 = 10.
N: r4 - 10 will always be negative until the loop ends @ r4 = 10.  This is set.
V: No signed overflow.  This is not set. 

Note in the chart:
lt  N != V 

In our loop N is set and V is not, so the "lt" condition works.


Next time we'll go further in-depth on exactly how the flags are set and what exactly is happening in the different compare instructions. Also, a bit on efficiency with compares and the logic behind turning conditions in a higher level language into assembly.