Assembly - JG/JNLE/JL/JNGE after CMP
Asked Answered
E

4

63

I don't understand the JG/JNLE/JL/JNGE instructions, which come after CMP.

for example, If I have:

CMP al,dl
jg label1

When al=101; dl =200.

On what we ask the jg? Is it on al>dl? or al-dl>0?

Same prolbem on the next code:

test al,dl
jg label1

I don't understand what we compare, and on what we ask the "jg".

In other words, I don't understand when we would jump to label1, and when we wouldn't.


For AT&T syntax, note that the subtraction does the other direction, so read right to left instead of left to right:

Erdda answered 8/3, 2012 at 12:46 Comment(1)
test with 2 different registers then jg is very weird. But since test/and always clears OF, it's like jns but also jumps on ZF=0. I assume this only exists in a made up example to get you to break down the flag-setting and flag-reading without any of the normal semantic meaning to help you figure out what it does. Anyway, test dl,dl / jg would be totally normal as a peephole optimization to save code size but otherwise be exactly like cmp dl,0 / jg.Mitchelmitchell
N
141

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
Norman answered 8/3, 2012 at 12:55 Comment(5)
Sorry, buy I check it now: If I have AL = 200, DL = 101. So, AL>DL, and for that, we need to jump (as you say..). But in this case, SF=0, OF=1, meaning -> SF!=OF, and jg would not take place. What I miss?Erdda
@Adam, because jg works on signed values (200 in eight bit two's complement is actually -56). If you want to handle them as unsigned values, use ja. I'll update the answer.Norman
It's worth noting that in gdb's syntax (GAS) the order of operands for cmp (cmpl) is inversed, as you can see on en.wikibooks.org/wiki/X86_Assembly/Control_Flow , which lists both Intel and GAS variants.Zincate
@Nickolay: That's called AT&T syntax. You can tell GNU tools to use GAS's flavour of Intel syntax, e.g. in your ~/.gdbinit, put set disassembly-flavor intel, or gcc -S -masm=intel.Mitchelmitchell
The conditions you list first, jo/jno and js/jns, are explicitly signed conditions. Signed overflow, and signed-negative result. They're not signed cmp comparisons, but they're very much signed conditions. (Well, you can use js to test for the MSB of a bitfield.) Maybe what you want is a different way to describe that grouping, instead of saying that signedness isn't relevant/appropriate. Perhaps "ones that aren't signed or unsigned compares when used after CMP". Otherwise those could go after the jg/jl group at the end, as sort of part of it.Mitchelmitchell
B
5

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.

Biplane answered 8/3, 2012 at 12:55 Comment(0)
U
2

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).
Undeceive answered 15/6, 2019 at 20:26 Comment(0)
D
1

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.

Disport answered 8/3, 2012 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.