Step to UEFI (185)输出 RAX 值

最近在重温书本从基础的寄存器看起。进入64位的时代之后,EAX 之类的寄存器直接被扩展为64位【参考1】:

看到这里忽然想起来一个问题:如何在UEFI 下输出当前通用寄存器比如RAX的值?为了这个目标进行了一番研究。

首先,看看 Print 是如何工作的。为了达成这个目标,使用之前提到的方法,在 INF 加入 /FAsc   用来生成汇编语言对应的 Lst 文件,同时使用 /Od 关闭优化。

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc   /Od

下面的 Print 调用

        volatile UINT64  Value=0x1234567890ABCDEF;
        Print(L"%lx\n",Value);

对应的汇编语言是:

; 37   :         volatile UINT64  Value=0x1234567890ABCDEF;
  0000e	48 b8 ef cd ab
	90 78 56 34 12	 mov	 rax, 1311768467294899695 ; 1234567890abcdefH
  00018	48 89 44 24 28	 mov	 QWORD PTR Value$[rsp], rax
; 38   :         Print(L"%lx\n",Value);
  0001d	48 8b 54 24 28	 mov	 rdx, QWORD PTR Value$[rsp]
  00022	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00029	e8 00 00 00 00	 call	 Print

可以看到,带有2个参数的Print 函数,使用 rdx作为第一个参数给出要显示的数值,rcx作为第二个参数给出显示的格式。

接下来做一个简单的试验,来修改这个值。使用 UltraEdit 打开 pa.efi 搜索“48 b8 ef cd ab 90 78 56 34 12”

修改为

运行结果如下:

如果能够在代码中做到这样的动作,那么就可以实现显示 RAX 的取值了。

接下来,我们将 Print 写成一个函数:

void
ShowRAX(UINT64 value)
{
        Print(L"%lx\n",value);
}

对应汇编为

ShowRAX	PROC						; COMDAT
; 13   : {
$LN3:
  00000	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  00005	48 83 ec 28	 sub	 rsp, 40			; 00000028H
  00009	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]
  0000e	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00015	e8 00 00 00 00	 call	 Print

; 20   : }

  0001a	48 83 c4 28	 add	 rsp, 40			; 00000028H
  0001e	c3		 ret	 0
ShowRAX	ENDP

可以看到要显示的值是在函数偏移0009 的地方,如果能将这个位置替换为我们要显示的寄存器即可。

  00009	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]

比如,我们给 RCX 赋值,然后显示出来。这里参考之前提到的汇编到机器码的转换方法【参考1】,编写一个汇编语言程序段:

 [BITS 64] 
mov rax,0xFEDCBA0987654321

使用 Nasm 取得对应的机器码:

48B92143658709BADC-     mov rcx,0xFEDCBA0987654321FE  

这里提到的机器码比 mov  rdx, QWORD PTR value$[rsp]要长,因此我们还需要合适的内容来填充。我请教了一下天杀,他说“VS现在加入了很多伪指令,在intrin.h文件中,比如__readcr0(),__readmsr(),__inbyte(),__cpuidex()”因此,这里直接使用 __nop() 对应机器码 0x90 作为填充。

具体做法是将函数ShowRAX地址赋值给一个指针,然后用这个指针来对内存中ShowRAX() 函数起始地址+9 的内存写入我们需要的指令。需要注意的是这样使用指针会导致一个 Warning 同样需要在[BuildOptions] 设定忽略它。

最终的 INF 如下:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = pa
  FILE_GUID                      = a912f198-7f0e-4803-b90A-b757b806ec84
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  PrintAsm.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiLib
  ShellCEntryLib
  IoLib

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc /wd4054 /Od

C文件如下:

#include  <Uefi.h>
#include  <Library/BaseLib.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/IoLib.h>

extern  EFI_SYSTEM_TABLE    *gST;
extern  EFI_BOOT_SERVICES   *gBS;

void
ShowRAX(UINT64 value)
{
        __nop();
        __nop();
        __nop();
        __nop();
        __nop();
        Print(L"%lx\n",value);
}

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        UINT8 *f=(UINT8 *)&ShowRAX;;
        volatile UINT64  Value=0x1234567890ABCDEF;
        Print(L"%lx\n",Value);
        *(f+9 )=0x48;
        *(f+10)=0xBA;
        *(f+11)=0x21;
        *(f+12)=0x43;
        *(f+13)=0x65;
        *(f+14)=0x87;
        *(f+15)=0x09;
        *(f+16)=0xBA;
        *(f+17)=0xDC;
        *(f+18)=0xFE;      
        ShowRAX(Value);
        return(0);
}

对应的 COD 文件如下:

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 

include listing.inc

INCLUDELIB OLDNAMES

PUBLIC	??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@ ; `string'
;	COMDAT ??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@ DB '%', 00H, 'l', 00H, 'x'
	DB	00H, 0aH, 00H, 00H, 00H			; `string'
PUBLIC	ShowRAX
PUBLIC	ShellAppMain
;	COMDAT pdata
pdata	SEGMENT
$pdata$ShowRAX DD imagerel $LN3
	DD	imagerel $LN3+36
	DD	imagerel $unwind$ShowRAX
pdata	ENDS
;	COMDAT pdata
pdata	SEGMENT
$pdata$ShellAppMain DD imagerel $LN3
	DD	imagerel $LN3+165
	DD	imagerel $unwind$ShellAppMain
pdata	ENDS
;	COMDAT xdata
xdata	SEGMENT
$unwind$ShellAppMain DD 010e01H
	DD	0620eH
xdata	ENDS
;	COMDAT xdata
xdata	SEGMENT
$unwind$ShowRAX DD 010901H
	DD	04209H
; Function compile flags: /Odsp
; File c:\201903\apppkg\applications\printasm\printasm.c
;	COMDAT ShellAppMain
_TEXT	SEGMENT
f$ = 32
Value$ = 40
Argc$ = 64
Argv$ = 72
ShellAppMain PROC					; COMDAT

; 35   : {

$LN3:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	48 83 ec 38	 sub	 rsp, 56			; 00000038H

; 36   :         UINT8 *f=(UINT8 *)&ShowRAX;;

  0000e	48 8d 05 00 00
	00 00		 lea	 rax, OFFSET FLAT:ShowRAX
  00015	48 89 44 24 20	 mov	 QWORD PTR f$[rsp], rax

; 37   :         volatile UINT64  Value=0x1234567890ABCDEF;

  0001a	48 b8 ef cd ab
	90 78 56 34 12	 mov	 rax, 1311768467294899695 ; 1234567890abcdefH
  00024	48 89 44 24 28	 mov	 QWORD PTR Value$[rsp], rax

; 38   :         Print(L"%lx\n",Value);

  00029	48 8b 54 24 28	 mov	 rdx, QWORD PTR Value$[rsp]
  0002e	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  00035	e8 00 00 00 00	 call	 Print

; 39   :         *(f+9 )=0x48;

  0003a	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0003f	c6 40 09 48	 mov	 BYTE PTR [rax+9], 72	; 00000048H

; 40   :         *(f+10)=0xBA;

  00043	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00048	c6 40 0a ba	 mov	 BYTE PTR [rax+10], 186	; 000000baH

; 41   :         *(f+11)=0x21;

  0004c	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00051	c6 40 0b 21	 mov	 BYTE PTR [rax+11], 33	; 00000021H

; 42   :         *(f+12)=0x43;

  00055	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0005a	c6 40 0c 43	 mov	 BYTE PTR [rax+12], 67	; 00000043H

; 43   :         *(f+13)=0x65;

  0005e	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00063	c6 40 0d 65	 mov	 BYTE PTR [rax+13], 101	; 00000065H

; 44   :         *(f+14)=0x87;

  00067	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0006c	c6 40 0e 87	 mov	 BYTE PTR [rax+14], 135	; 00000087H

; 45   :         *(f+15)=0x09;

  00070	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00075	c6 40 0f 09	 mov	 BYTE PTR [rax+15], 9

; 46   :         *(f+16)=0xBA;

  00079	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  0007e	c6 40 10 ba	 mov	 BYTE PTR [rax+16], 186	; 000000baH

; 47   :         *(f+17)=0xDC;

  00082	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00087	c6 40 11 dc	 mov	 BYTE PTR [rax+17], 220	; 000000dcH

; 48   :         *(f+18)=0xFE;      

  0008b	48 8b 44 24 20	 mov	 rax, QWORD PTR f$[rsp]
  00090	c6 40 12 fe	 mov	 BYTE PTR [rax+18], 254	; 000000feH

; 49   :         ShowRAX(Value);

  00094	48 8b 4c 24 28	 mov	 rcx, QWORD PTR Value$[rsp]
  00099	e8 00 00 00 00	 call	 ShowRAX

; 50   :         return(0);

  0009e	33 c0		 xor	 eax, eax

; 51   : }

  000a0	48 83 c4 38	 add	 rsp, 56			; 00000038H
  000a4	c3		 ret	 0
ShellAppMain ENDP
_TEXT	ENDS
; Function compile flags: /Odsp
; File c:\201903\apppkg\applications\printasm\printasm.c
;	COMDAT ShowRAX
_TEXT	SEGMENT
value$ = 48
ShowRAX	PROC						; COMDAT

; 12   : {

$LN3:
  00000	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  00005	48 83 ec 28	 sub	 rsp, 40			; 00000028H

; 13   :         __nop();

  00009	90		 npad	 1

; 14   :         __nop();

  0000a	90		 npad	 1

; 15   :         __nop();

  0000b	90		 npad	 1

; 16   :         __nop();

  0000c	90		 npad	 1

; 17   :         __nop();

  0000d	90		 npad	 1

; 18   :         Print(L"%lx\n",value);

  0000e	48 8b 54 24 30	 mov	 rdx, QWORD PTR value$[rsp]
  00013	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_19LKIJIFCB@?$AA?$CF?$AAl?$AAx?$AA?6?$AA?$AA@
  0001a	e8 00 00 00 00	 call	 Print

; 19   : }

  0001f	48 83 c4 28	 add	 rsp, 40			; 00000028H
  00023	c3		 ret	 0
ShowRAX	ENDP
_TEXT	ENDS
END

运行结果:

完整的代码下载:

有了上面的方法,我们能够灵活的在代码中插入需要的汇编语句,对于以后的研究大有裨益。如果你有更好的方法不妨分享一下。

参考:

1. http://sandpile.org/x86/gpr.htm  x86 architecture general purpose registers

2. www.lab-z.com/asm2mach/ 汇编到机器码的转换

《Step to UEFI (185)输出 RAX 值》有2个想法

  1. 函数指针转换成数据指针是 undefined behavior:https://stackoverflow.com/a/559671
    C 指针类型转换有 strict aliasing rule:https://stackoverflow.com/a/99010。此外另一个答案提到不同类型的指针拷贝(在 C 和 C++ 中同时是)正确做法是调用 memcpy:https://stackoverflow.com/a/51228315

    我觉得应该是写一个汇编代码实现 show_rax(),在 C 里面声明函数原型,然后联合编译。本质上文章里面的做法是在手工模拟链接器的行为(链接器做的就是把机器指令给拼起来的工作),而且这样做很容易出问题(比如假如你开优化,或者换编译器)。

发表回复

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