Assembly - JG/JNLE/JL/JNGE after CMP
Addition and subtraction in two's complement is the same for signed and unsigned numbers
The key observation is that CMP is basically subtraction, and:
In two's complement (integer representation used by x86), signed and unsigned addition are exactly the same operation
This allows for example hardware developers to implement it more efficiently with just one circuit.
So when you give input bytes to the x86 ADD instruction for example, it does not care if they are signed or not.
However, ADD does set a few flags depending on what happened during the operation:
carry: unsigned addition or subtraction result does not fit in bit size, e.g.: 0xFF + 0x01 or 0x00 - 0x01
For addition, we would need to carry 1 to the next level.
sign: result has top bit set. I.e.: is negative if interpreted as signed.
overflow: input top bits are both 0 and 0 or 1 and 1 and output inverted is the opposite.
I.e. signed operation changed sigedness in an impossible way (e.g. positive + positive or negative
We can then interpret those flags in a way that makes comparison match our expectations for signed or unsigned numbers.
This interpretation is exactly what JA vs JG and JB vs JL do for us!
Code example
Here is GNU GAS a code snippet to make this more concrete:
/* 0x0 ==
*
* * 0 in 2's complement signed
* * 0 in 2's complement unsigned
*/
mov $0, %al
/* 0xFF ==
*
* * -1 in 2's complement signed
* * 255 in 2's complement unsigned
*/
mov $0xFF, %bl
/* Do the operation "Is al < bl?" */
cmp %bl, %al
Note that AT&T syntax is "backwards": mov src, dst
. So you have to mentally reverse the operands for the condition codes to make sense with cmp
. In Intel syntax, this would be cmp al, bl
After this point, the following jumps would be taken:
- JB, because 0 < 255
- JNA, because !(0 > 255)
- JNL, because !(0 < -1)
- JG, because 0 > -1
Note how in this particular example the signedness mattered, e.g. JB is taken but not JL.
Runnable example with assertions.
Equals / Negated versions like JLE / JNG are just aliases
By looking at the Intel 64 and IA-32 Architectures Software Developer's Manuals Volume 2 section "Jcc - Jump if Condition Is Met" we see that the encodings are identical, for example:
Opcode Instruction Description
7E cb JLE rel8 Jump short if less or equal (ZF=1 or SF ≠ OF).
7E cb JNG rel8 Jump short if not greater (ZF=1 or SF ≠ OF).
Wikibooks has a fairly good summary of jump instructions. Basically, there's actually two stages:
cmp_instruction op1, op2
Which sets various flags based on the result, and
jmp_conditional_instruction address
which will execute the jump based on the results of those flags.
Compare (cmp
) will basically compute the subtraction op1-op2
, however, this is not stored; instead only flag results are set. So if you did cmp eax, ebx
that's the same as saying eax-ebx
- then deciding based on whether that is positive, negative or zero which flags to set.
More detailed reference here.
When you do a cmp a,b
, the flags are set as if you had calculated a - b
. Then the conditional jump instructions check those flags to see if the jump should be made.
In other words, the first block of code you have (with my comments added):
cmp al, dl ; set flags based on the comparison.
jg label1 ; then jump based on the flags.
would jump to label1
if and only if al
was greater than dl
.
You're probably better off thinking of it as al > dl
but the two choices you have there are mathematically equivalent:
al > dl
(al - dl) > (dl - dl) [subtract dl from both sides]
(al - dl) > 0 [cancel the terms on the right hand side]
You need to be careful when using jg
inasmuch as it assumes your values were signed. So, if you compare the bytes 101
(101
in two's complement) with 200
(-56
in two's complement), the former will actually be greater. If that's not what was desired, you should use the equivalent unsigned comparison.
See here for more detail on jump selection, reproduced below for completeness, in the order of:
- ones that aren't signed or unsigned compares when used after CMP.
- unsigned ones.
- signed ones.
Instruction | Description | Signed-ness | Conditions checked |
---|---|---|---|
JO | Jump if overflow | OF = 1 | |
JNO | Jump if not overflow | OF = 0 | |
JS | Jump if sign | SF = 1 | |
JNS | Jump if not sign | SF = 0 | |
JE JZ |
Jump if equal Jump if zero |
ZF = 1 | |
JNE JNZ |
Jump if not equal Jump if not zero |
ZF = 0 | |
JP JPE |
Jump if parity Jump if parity even |
PF = 1 | |
JNP JPO |
Jump if no parity Jump if parity odd |
PF = 0 | |
JCXZ JECXZ JRCXZ |
Jump if CX is zero Jump if ECX is zero Jump if RCX is zero |
CX = 0 ECX = 0 RCX = 0 |
|
JB JNAE JC |
Jump if below Jump if not above or equal Jump if carry |
unsigned | CF = 1 |
JNB JAE JNC |
Jump if not below Jump if above or equal Jump if not carry |
unsigned | CF = 0 |
JBE JNA |
Jump if below or equal Jump if not above |
unsigned | CF = 1 or ZF = 1 |
JA JNBE |
Jump if above Jump if not below or equal |
unsigned | CF = 0 and ZF = 0 |
JL JNGE |
Jump if less Jump if not greater or equal |
signed | SF <> OF |
JGE JNL |
Jump if greater or equal Jump if not less |
signed | SF = OF |
JLE JNG |
Jump if less or equal Jump if not greater |
signed | ZF = 1 or SF <> OF |
JG JNLE |
Jump if greater Jump if not less or equal |
signed | ZF = 0 and SF = OF |
The command JG simply means: Jump if Greater. The result of the preceding instructions is stored in certain processor flags (in this it would test if ZF=0 and SF=OF) and jump instruction act according to their state.