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

看到这里忽然想起来一个问题:如何在UEFI 下输出当前通用寄存器比如RAX的值?为了这个目标进行了一番研究。
首先,看看 Print 是如何工作的。为了达成这个目标,使用之前提到的方法,在 INF 加入 /FAsc 用来生成汇编语言对应的 Lst 文件,同时使用 /Od 关闭优化。
1 2 | [BuildOptions] MSFT:*_*_X64_CC_FLAGS = /FAsc /Od |
下面的 Print 调用
1 2 | volatile UINT64 Value=0x1234567890ABCDEF; Print( L"%lx\n" ,Value); |
对应的汇编语言是:
1 2 3 4 5 6 7 8 9 | ; 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 写成一个函数:
1 2 3 4 5 | void ShowRAX( UINT64 value) { Print( L"%lx\n" ,value); } |
对应汇编为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 的地方,如果能将这个位置替换为我们要显示的寄存器即可。
1 | 00009 48 8b 54 24 30 mov rdx, QWORD PTR value$[rsp] |
比如,我们给 RCX 赋值,然后显示出来。这里参考之前提到的汇编到机器码的转换方法【参考1】,编写一个汇编语言程序段:
1 2 | [BITS 64] mov rax,0xFEDCBA0987654321 |
使用 Nasm 取得对应的机器码:
1 | 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 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | [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文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #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 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | ; 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()