3

I have this function (RDRand - written by David Heffernan) that seam to work ok in 32 bit, but failed in 64 bit :

function TryRdRand(out Value: Cardinal): Boolean;
{$IF defined(CPU64BITS)}
asm .noframe
{$else}
asm
{$ifend}
  db   $0f
  db   $c7
  db   $f1
  jc   @success
  xor  eax,eax
  ret
@success:
  mov  [eax],ecx
  mov  eax,1
end;

doc of the function is here: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide

Especially it's written :

Essentially, developers invoke this instruction with a single operand: the destination register where the random value will be stored. Note that this register must be a general purpose register, and the size of the register (16, 32, or 64 bits) will determine the size of the random value returned.

After invoking the RDRAND instruction, the caller must examine the carry flag (CF) to determine whether a random value was available at the time the RDRAND instruction was executed. As Table 3 shows, a value of 1 indicates that a random value was available and placed in the destination register provided in the invocation. A value of 0 indicates that a random value was not available. In current architectures the destination register will also be zeroed as a side effect of this condition.

My knowledge of ASM is quite low, what did I miss ?

Also I do not quite understand this instruction :

  ...
  xor  eax,eax
  ret
  ...

What it's does exactly ?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
zeus
  • 12,173
  • 9
  • 63
  • 184
  • 1
    `xor eax,eax` stores 0 in the EAX register, which corresponds to the `False` return value. – Olivier Mar 18 '21 at 21:37
  • @Olivier thank, so now I understand this part :) and I guess mov eax,1 store True in the result :) – zeus Mar 18 '21 at 21:49
  • 1
    That manual `db` stuff apparently encodes `rdrand ecx` (in both 32 and 64-bit mode). If you want `rdrand rcx`, you can add a REX.W=1 prefix in front of it (`db $48`). You'll also have to find out what register Delphi uses to pass a pointer input, e.g. `mov [rax], ecx` or `rcx`. – Peter Cordes Mar 18 '21 at 21:49
  • 1
    Yeah, a simpler way to create the boolean return value would be `setc al`. (If Delphi wants that zero-extended to a full reg, you'd want to xor-zero EAX before the rdrand sets flags, so it's less convenient if the pointer input is in the same retval register). If you're not branching, you'd just always store the integer result, even if RDRAND says it's invalid. Or you could hide the RDRAND retry inside this function by looping on RDRAND success, looping on `retry: rdrand rax` / `jnc retry` / `ret` – Peter Cordes Mar 18 '21 at 21:54
  • @PeterCordes I tried db $48 db $0f db $c7 db $f1 .... @ success: mov [rax],rcx mov rax,1 but it's failed :( ...... I know that On x64 the address would arrive in rcx, iirc – zeus Mar 18 '21 at 22:15
  • Well, *does* Delphi pass the first operand in RAX? Check the docs, or single-step with a debugger after passing some unique bit-pattern like 0xcccccccccc or `0xdeadbeef` as a pointer. Also, if this is *inline* asm, I wouldn't be sure it's safe to `ret` along one path of execution but fall off the end in the other. That would only work if the compiler doesn't inline the function. IDK, maybe the compiler does just tack on a `ret` at the end. – Peter Cordes Mar 18 '21 at 22:19
  • 1
    @peter Compiler tacks a ret on the end. Calling convention is standard Windows x64. The db is because the Delphi compiler doesn't know rdrand. – David Heffernan Mar 18 '21 at 22:25
  • @DavidHeffernan: right, people sometimes do the same thing with `.byte` in GNU C inline asm in case of ancient assemblers. So yeah, I'd probably do `xor eax,eax` / `rdrand rdx` (encoded manually) / `mov [rcx], rdx` / `setc al` for a 64-bit integer output. – Peter Cordes Mar 18 '21 at 22:30
  • Or since it will probably succeed every time, you could branch but lay out the branches so the success case is the not-taken branch path. like `rdrand rax` / `jnc .return_zero` / `mov [rcx], rax` / `mov eax, 1` / `ret` then the error-return path. (Or a retry loop) – Peter Cordes Mar 19 '21 at 03:30
  • @PeterCordes yes when I just do db $0f, $c7, $f0 without checking then everything work fine in win32 and win64, it's just the mov [eax],ecx mov eax,1 that miserably fail in win64 and I don't understand by what I need to replace it – zeus Mar 19 '21 at 07:23
  • @DavidHeffernan as you wrote this function, you say that on x64 the address would arrive in rcx, iirc ? do you have some idea of what I can try to make it working ? – zeus Mar 19 '21 at 07:27

1 Answers1

6

If you want a function that performs exactly the same then I think that looks like this:

function TryRdRand(out Value: Cardinal): Boolean;
asm
{$if defined(WIN64)}
  .noframe
  // rdrand eax
  db   $0f
  db   $c7
  db   $f0
  jnc  @fail
  mov  [rcx],eax
{$elseif defined(WIN32)}
  // rdrand ecx
  db   $0f
  db   $c7
  db   $f1
  jnc  @fail
  mov  [eax],ecx
{$else}
{$Message Fatal 'TryRdRand not implemented for this platform'}
{$endif}
  mov  eax,1
  ret
@fail:
  xor  eax,eax
end;

The suggestion made by Peter Cordes of implementing a retry loop in the asm looks sensible to me. I will not attempt to implement that here, since I think it is somewhat outside the scope of your question.

Also, Peter points out that in x64 you can read a 64 bit random value with the REX.W=1 prefix. That would look like this:

function TryRdRand(out Value: NativeUInt): Boolean;
asm
{$if defined(WIN64)}
  .noframe
  // rdrand rax
  db   $48  // REX.W = 1
  db   $0f
  db   $c7
  db   $f0
  jnc  @fail
  mov  [rcx],rax
{$elseif defined(WIN32)}
  // rdrand ecx
  db   $0f
  db   $c7
  db   $f1
  jnc  @fail
  mov  [eax],ecx
{$else}
{$Message Fatal 'TryRdRand not implemented for this platform'}
{$endif}
  mov  eax,1
  ret
@fail:
  xor  eax,eax
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490