1

I want to generate a random 32-bit number. I am using rdrand for this. However, I am having some problems. Since the number can be no more than 32 bits large, I am doing rdrand eax. Here is where the problem arises:

  • I need to be able to refer to this 32-bit number in a 64-bit register since the rest of my codebase uses 64-bit registers. I figured that I could clear rax with a xor to itself, and then only load half of it with rdrand eax. Then I could look at rax, have one half be at most a 32-bit number, and the other half be cleared.

  • When I compare rax with the maximum 32-bit number size, 2147483647, I get very inconsistent results. Half of the time, the exit code is 1, meaning that the number in rax is, in fact, smaller than 32-bits. But the other half of the time I get 0. It's almost like the result in eax is not always less than or equal to 2147483647, which is unexpected given what this documentation says.

Does anyone know what is going wrong in my thought process? I am very curious to know. (Note: I'm assembling with Clang on macOS.)

    .global _main
    .text

# how to generate a 32-bit random number that is in rax?
_main:
    xor rax, rax  # clear the top half of the eax-rax register pair
    rdrand eax  # a 32-bit number in rax

    cmp rax, 2147483647
    jle smaller_than_32  # rax <= MAX_32_BIT
    xor rdi, rdi
    jmp end
    smaller_than_32:
        mov rdi, 1
    end:
        mov rax, 0x2000001
        syscall
Caspian Ahlberg
  • 934
  • 10
  • 19
  • 2
    `xor rax, rax` is pointless. `rdrand eax` implicitly zero-extends into RAX. [Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?](https://stackoverflow.com/q/11177137). That's why [`xor edi,edi` works](https://stackoverflow.com/questions/33666617/what-is-the-best-way-to-set-a-register-to-zero-in-x86-assembly-xor-mov-or-and) to zero the full register. Also you could `xor edi,edi` / `cmp eax, 2147483647` / `setg dil` instead of branching. Or just `test eax,eax` / `setns` to check the MSB via the sign flag. – Peter Cordes Nov 20 '20 at 18:52

1 Answers1

4

2147483647, or equivalently, 0x7FFFFFFF, is the maximum signed 32-bit number. rdrand eax can put any value from 0x00000000 to 0xFFFFFFFF in eax. You have a few choices for how to handle this:

  1. Compare it with 4294967295, or equivalently 0xFFFFFFFF, the maximum unsigned 32-bit number, instead.
  2. Do cmp eax, 2147483647 instead of cmp rax, 2147483647. Since jle operates on the result of comparing signed integers, this will cause what you're now seeing as 2147483648 through 4294967295 to instead be interpreted as -2147483648 through -1.
  3. Instead of having the upper 32 bits of rax always be zero, have them match the sign bit of eax instead (this is known as sign-extending). You can do this by doing movsx rax, eax right after rdrand eax. This will cause rax to hold a value between -2147483648 and 2147483647 instead of between 0 and 4294967295.

Any of those changes will result in your conditional jump always being taken, as you expect. If you want rax to end up being between 0 and 4294967295, then choose option 1 or 2. If you want rax to end up being between -2147483648 and 2147483647, then choose option 3.

  • 2
    Of course, once you understand that you're getting a 32-bit number, not a 31-bit number, you don't need to cmp/jle or `test eax,eax` / `jns` at all. Since you don't actually need to range-check `rdrand`'s output, option 1 and 2 are equivalent (use it raw, no movsx). I guess you could say that it's a matter of whether the rest of your program treats it as a 32-bit / 64-bit unsigned, or a 32-bit signed number. – Peter Cordes Nov 20 '20 at 19:00