Step to UEFI (185)输出 RAX 值

最近在重温书本从基础的寄存器看起。进入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/ 汇编到机器码的转换

《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 里面声明函数原型,然后联合编译。本质上文章里面的做法是在手工模拟链接器的行为(链接器做的就是把机器指令给拼起来的工作),而且这样做很容易出问题(比如假如你开优化,或者换编译器)。

回复 sah3Zoo6 取消回复

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