问题描述
我有这个函数(rdrand - 由 David Heffernan 编写),它可以在 32 位中正常工作,但在 64 位中失败:
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;
函数的文档在这里:https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
特别是这样写:
本质上,开发人员使用单个操作数调用此指令: 将存储随机值的目标寄存器。笔记 这个寄存器必须是一个通用寄存器,并且大小 寄存器(16、32 或 64 位)将决定 返回随机值。
调用 rdrand 指令后,调用者必须检查 进位标志 (CF) 以确定随机值是否可用 rdrand 指令执行的时间。如表 3 所示,一个 值为 1 表示随机值可用并放入 调用中提供的目标寄存器。值为 0 表示随机值不可用。现在 目标寄存器也将作为一侧归零 这种情况的影响。
我对 ASM 的了解很低,我错过了什么?
我也不太明白这个指令:
...
xor eax,eax
ret
...
它到底是做什么的?
解决方法
如果你想要一个执行完全相同的函数,那么我认为它看起来像这样:
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;
Peter Cordes 提出的在 asm 中实现重试循环的建议在我看来很明智。我不会尝试在这里实施,因为我认为这有点超出了您的问题范围。
此外,Peter 指出,在 x64 中,您可以读取带有 REX.W=1 前缀的 64 位随机值。看起来像这样:
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],eax
end;