Step to UEFI (127)RDRAND 再研究

前面提到过 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” 有如下介绍:

rd1

从上面可以看到 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 决定列,然后红色框中的是对应的寄存器

rd2

具体来说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

 

运行结果如下:

rd3

完整的代码下载:

RdRand2

最后还有第三个问题:从上面可以看到0fh, 0c7h, 0f7h 可以对应 RDRAND eax 也可以对应到 RDRAND ax,那么是如何进行区别的呢?
我猜测是根据当前CPU 的状态决定的,当现在的代码段是32位,这个机器码操作的目标是 EAX,如果是16位,操作目标就是ax。 当然,对于这一点我现在无法确认,后面会想办法进行验证。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注