1

I was wondering how cmp is used outside of saving a byte on doing a zero-check on a register, for example:

cmp $0, %eax

vs.

test %eax, %eax

I was doing some trial-and-error and it seems like this is its main usage (unless I'm missing something -- if any further usage can use the and instruction).

Are there other uses of the test instruction?


Somewhat related: In x86 what's difference between "test eax,eax" and "cmp eax,0".


Here are some samples I did:

   mov $1, %eax
   mov $2, %ebx
   test %eax, %ebx
   jg _start       # <-- doesn't jump
   jl _start       # <-- doesn't jump

   mov $1, %eax
   mov $1, %ebx
   test %eax, %ebx
   je _start       # <-- doesn't jump

   mov $0, %eax
   mov $1, %ebx
   test %eax, %ebx
   je _start       # <-- why is this the only one that works? 1 & 0 == 0 ?
samuelbrody1249
  • 4,379
  • 1
  • 15
  • 58
  • 1
    Hi interesting ,not sure if this might be of interest https://cboard.cprogramming.com/tech-board/151751-when-use-test-vs-cmp-assembly-code.html – IronMan Sep 23 '20 at 23:57
  • 4
    The use-cases for `test` other than `same,same` are mostly with an immediate to test a specific bit, e.g. `test $1, %al` / `jnz odd_number` without actually destroying it with AND. `jg` and `jl` don't make much semantic sense after `test`, but `jz` and `jnz` do. – Peter Cordes Sep 24 '20 at 00:12
  • 3
    You mention AND being able to do anything TEST can. Yes, but you could say the same about SUB making CMP redundant. CMP is just a SUB that only writes FLAGS, not the "destination" operand. – Peter Cordes Sep 24 '20 at 00:13
  • 1
    @PeterCordes I see, so you could do something like this: `mov $0b11, %eax` `test $0b10, %eax` `jnz some_label` to test if the second bit is flagged or not without modifying the value being tested. Is that what you mean? – samuelbrody1249 Sep 24 '20 at 00:17

1 Answers1

5

Are there other uses of the test instruction?

Uses of the test instruction include:

  • Checking if a value is zero. E.g. if(x == 0) -> test eax,eax

  • Checking if a value is divisible by any power of 2. E.g. if(x % 8 == 0) -> test eax,7

  • Checking specific bit/s. E.g. if(x & (S_IREAD | S_IWRITE) == 0) -> test eax,(S_IREAD | S_IWRITE). Note: This is probably the "main intended use".

  • Various cases where 2 or more smaller values are packed together. E.g. if you have two signed 16-bit values packed into EAX then you could test eax,0x80008000 to determine if either of the 16-bit values are negative; if you're supposed to have four ASCII characters packed into EAX you could test eax,0x80808080 to determine if they're all valid ASCII; if you have three 10-bit numbers packed into EAX and want to check that they're all smaller than 512 you could test eax,(0x200 << 20) | (0x200 << 10) | 0x200 ; if you have six 5-bit values packed into EAX and want to check that the 2nd value is larger than 8 you could test eax, (0x18 << 6). Of course this can be extended to use more registers (e.g. pack sixteen 6-bit values into EDX:EBX:EAX and use three test instructions to ...) and/or augmented with other instructions (e.g. maybe not before the test). Note: These cases would work the same on single values, but there's better/simpler alternatives for single values (mostly cmp), and the main benefit is avoiding the need to unpack.

Here are some samples I did:

There's multiple flags (CF/carry, OF/overflow, SF/sign, ZF/zero, AF/auxiliary, PF/parity) and different "Jcc" conditional branches use different flags (e.g. jc uses CF, js uses SF, etc). All of the "bitwise" instructions (AND, OR, XOR, TEST) clear CF and OF and leave AF undefined, so any conditional branches that rely on those flags don't make sense (e.g. jl relies on SF and OF but OF is always cleared by test, so jl doesn't make sense after test).

Your second sample works but shouldn't jump (it's like "if(1 & 1 == 0) goto __start;" which does nothing). Your third sample works and should jump.

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • "e.g. `jcc` uses CF," -- you meant `jc`. Three 11 bit numbers are 33 bits so they don't fit into a 32-bit GPR. – ecm Sep 24 '20 at 05:01
  • 1
    @Brendan thanks for this great answer, but one question. How do you get the `0x600` in the test to check the 10-bit number? I got `0x200` on my side -- isn't the max 10-bit number `1024` and `0x600` is `1536` ? (And how'd you get `0x18` ?) Anyways, thanks for your help! – samuelbrody1249 Sep 26 '20 at 05:29
  • 1
    @samuelbrody1249: Ah - you're right. Fixed that too :-) – Brendan Sep 26 '20 at 09:24