最近在重温书本从基础的寄存器看起。进入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/ 汇编到机器码的转换
函数指针转换成数据指针是 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 里面声明函数原型,然后联合编译。本质上文章里面的做法是在手工模拟链接器的行为(链接器做的就是把机器指令给拼起来的工作),而且这样做很容易出问题(比如假如你开优化,或者换编译器)。
是的,应该模仿其他汇编的方式编写一个 show_rax()