Search code examples
x86x86-64cpu-registers32-bitrdrand

Generate a random 32-bit number to store in a 64-bit register


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

Solution

  • 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.