前面提到过 RDRAND指令,最近再拿出来看一下,思考了更多问题。
前情提要:
具体的RDRAND指令是通过内嵌汇编实现的,在ASM中直接定义如下
; rdrand ax ; generate a 16 bit RN into ax, CF=1 if RN generated ok, otherwise CF=0 db 0fh, 0c7h, 0f0h ; rdrand r16: "0f c7 /6 ModRM:r/m(w)"
第一个问题是:这些数字是哪里来的,如何对应到这个指令上的?这种事情必须查阅 Intel 手册了,在“Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B, 2C & 2D): Instruction Set Reference, A-Z” 有如下介绍:
从上面可以看到 0F C7 /6 就是 RDRAND指令。
接下来是第二个问题:这组数字是如何决定指令使用的寄存器的?意思是:如何确定当前机器代码是rdrand ax 还是rdrand bx?
这个问题就涉及到了指令编码 ModR/M 的知识。在研究了下面的资料之后
http://www.mouseos.com/x64/index.html x86/x64 指令编码内幕(适用于 AMD/Intel)
http://blog.csdn.net/aap159951/article/details/48706207 自己的反汇编引擎--Intel指令编码 (2)
配合下面的表格:0F C7 /6 中的 /6 决定列,然后红色框中的是对应的寄存器
具体来说0F C7 F1 表示 RDRAND ecx;0F C7 F7 表示 RDRAND edi。
接下来就是如何验证上面的猜想。我们使用 0f 0c7 f3 来验证是否为 RDRAND EBX,使用 0f 0c7 f7 来验证是否为 RDRAND EDI。
为了配合内嵌汇编,我在INF 中设置打开生成的 COD文件,这样可以方便的看清楚参数的传递。
[BuildOptions] MSFT:*_*_X64_CC_FLAGS = /Oi- /FAcs /Od
这样在编译的时候会生成 cod文件,可以在\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\ 的对应目录下找到
从里面可以看到,通过 rcx传递进去地址,返回值也会在对应的地址中,同时判断 eax是否成功。
; 143 : if (RdRand32StepEDI (Rand)) { 00046 48 8b 4c 24 40 mov rcx, QWORD PTR Rand$[rsp] 0004b e8 00 00 00 00 call RdRand32StepEDI 00050 0f b6 c0 movzx eax, al 00053 85 c0 test eax, eax 00055 74 04 je SHORT $LN1@RdRand32ED ; 144 : return EFI_SUCCESS;
有了上面的实验,即可编写代码。需要特别注意三点:一个是不要破坏寄存器,否则可能出现不可预料的后果;另外一个是当函数接收参数只有一个时,会使用rcx来传递参数【这部分的详细资料目前没有查到,有了解的朋友欢迎在留言中补充】;最后的结果会从 rcx 传出,在这个例子中是传输到 rcx 给出的地址中。
最终的代码如下:
;------------------------------------------------------------------------------ ; Generate a 32 bit random number ; Return TRUE if Rand generated successfully, or FALSE if not ; ; BOOLEAN EFIAPI RdRand32Step (UINT32 *Rand); RCX ;------------------------------------------------------------------------------ RdRand32StepEBX PROC ; rdrand ebx ; generate a 32 bit RN into eax, CF=1 if RN generated ok, otherwise CF=0 push rbx db 0fh, 0c7h, 0f3h ; rdrand r32: "0f c7 /6 ModRM:r/m(w)" jb rn32_ok ; jmp if CF=1 xor ebx, ebx ; reg=0 if CF=0 pop rbx ret ; return with failure status rn32_ok: mov [rcx], ebx mov rax, 1 pop rbx ret RdRand32StepEBX ENDP ;------------------------------------------------------------------------------ ; Generate a 32 bit random number ; Return TRUE if Rand generated successfully, or FALSE if not ; ; BOOLEAN EFIAPI RdRand32Step (UINT32 *Rand); RCX ;------------------------------------------------------------------------------ RdRand32StepEDI PROC ; rdrand edi ; generate a 32 bit RN into eax, CF=1 if RN generated ok, otherwise CF=0 push rdi db 0fh, 0c7h, 0f7h ; rdrand r32: "0f c7 /6 ModRM:r/m(w)" jb rn32_ok ; jmp if CF=1 xor edi, edi ; reg=0 if CF=0 pop rdi ret ; return with failure status rn32_ok: mov [rcx], edi mov rax, 1 pop rdi ret RdRand32StepEDI ENDP
运行结果如下:
完整的代码下载:
最后还有第三个问题:从上面可以看到0fh, 0c7h, 0f7h 可以对应 RDRAND eax 也可以对应到 RDRAND ax,那么是如何进行区别的呢?
我猜测是根据当前CPU 的状态决定的,当现在的代码段是32位,这个机器码操作的目标是 EAX,如果是16位,操作目标就是ax。 当然,对于这一点我现在无法确认,后面会想办法进行验证。