-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathDelphi.RdRnd.pas
282 lines (253 loc) · 7.96 KB
/
Delphi.RdRnd.pas
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
unit Delphi.RdRnd;
interface
type
TRDRANDAvailable = record
RDRAND: Boolean; // True if RDRAND is available
RDSEED: Boolean; // True if RDSEED is available
end;
{$IF Defined(WIN64)}
function RDSEED64(const ARetryCount: UInt64 = 10): UInt64;
function RDRAND64(const ARetryCount: UInt64 = 10): UInt64;
{$ENDIF}
{$IF Defined(WIN32)}
function RDSEED32(const ARetryCount: UInt32 = 10): UInt32;
function RDRAND32(const ARetryCount: UInt32 = 10): UInt32;
function RDSEED64(const ARetryCount: UInt32 = 10): UInt64;
function RDRAND64(const ARetryCount: UInt32 = 10): UInt64;
{$ENDIF}
var
RDInstructionsAvailable: TRDRANDAvailable;
implementation
const
//ID string to identify CPU Vendor, the are a multitude .. but we focalize on this
VendorIDxIntel: array [0..11] of AnsiChar = 'GenuineIntel';
VendorIDxAMD: array [0..11] of AnsiChar = 'AuthenticAMD';
{$IF Defined(WIN64)}
function RDSEED64(const ARetryCount: UInt64 = 10): UInt64;
asm
.noframe
mov RDX, ARetryCount
inc RDX
@LOOP:
dec RDX
js @Exit
DB $48, $0F, $C7, $F8 // RDSEED RAX
jnc @LOOP
@EXIT:
end;
{$ENDIF}
{$IF Defined(WIN64)}
function RDRAND64(const ARetryCount: UInt64 = 10): UInt64;
asm
.noframe
mov RDX, ARetryCount
inc RDX
@LOOP:
dec RDX
js @Exit
DB $48, $0F, $C7, $F0 // RDRAND RAX
jnc @LOOP
@EXIT:
end;
{$ENDIF}
{$IF Defined(WIN32)}
function RDSEED32(const ARetryCount: UInt32 = 10): UInt32;
asm
inc edx
@LOOP:
dec edx
js @Exit
DB $0F, $C7, $F8 // RDSEED EAX
jnc @LOOP
@EXIT:
end;
{$ENDIF}
{$IF Defined(WIN32)}
function RDRAND32(const ARetryCount: UInt32 = 10): UInt32;
asm
inc edx
@LOOP:
dec edx
js @Exit
DB $48, $0F, $C7, $F0 // RDRAND EAX
jnc @LOOP
@EXIT:
end;
{$ENDIF}
{$IF Defined(WIN32)}
function RDSEED64(const ARetryCount: UInt32 = 10): UInt64;
var
LValue1: UInt32;
LValue2: UInt32;
begin
LValue1 := RDSEED32(ARetryCount);
LValue2 := RDSEED32(ARetryCount);
Result := UInt64(LValue1) shl 32 or LValue2;
end;
{$ENDIF}
{$IF Defined(WIN32)}
function RDRAND64(const ARetryCount: UInt32 = 10): UInt64;
var
LValue1: UInt32;
LValue2: UInt32;
begin
LValue1 := RDRAND32(ARetryCount);
LValue2 := RDRAND32(ARetryCount);
Result := UInt64(LValue1) shl 32 or LValue2;
end;
{$ENDIF}
{
Internal functions, may be usefull to implement other check
Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit),
not tested in real address mode
The Intel Documentation has more detail about CPUID
Jedi project has implemented TCPUInfo with more details.
First check that the CPU supports CPUID instructions. There are some exceptions with this rule,
but with very very old processors
}
function IsCPUIDValid: Boolean; register;
asm
{$IFDEF WIN64}
pushfq //Save EFLAGS
pushfq //Store EFLAGS
xor qword [esp], $00200000 //Invert the ID bit in stored EFLAGS
popfq //Load stored EFLAGS (with ID bit inverted)
pushfq //Store EFLAGS again (ID bit may or may not be inverted)
pop rax //eax = modified EFLAGS (ID bit may or may not be inverted)
xor rax, qword [esp] //eax = whichever bits were changed
popfq //Restore original EFLAGS
and RAX, $00200000 //eax = zero if ID bit can't be changed, else non-zero
jz @quit
mov RAX, $01 //If the Result is Boolean, the return parameter should be in A??? (true if A??? <> 0)
@quit:
{$ELSE}
pushfd //Save EFLAGS
pushfd //Store EFLAGS
xor dword [esp], $00200000 //Invert the ID bit in stored EFLAGS
popfd //Load stored EFLAGS (with ID bit inverted)
pushfd //Store EFLAGS again (ID bit may or may not be inverted)
pop eax //eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] //eax = whichever bits were changed
popfd //Restore original EFLAGS
and eax, $00200000 //eax = zero if ID bit can't be changed, else non-zero
jz @quit
mov EAX, $01 //If the Result is Boolean, the return parameter should be in AL (true if AL <> 0)
@quit:
{$ENDIF}
end;
{
1) Check that the CPU is an INTEL CPU, we don't know nothing about other's
We can presume the AMD modern processors have the same check of INTEL, but only for some instructions.
No test were made to verify this (no AMD processor available)
2) Catch the features of the CPU in use
3) Catch the new features of the CPU in use
}
procedure CPUIDGeneralCall(AInEAX: Cardinal; AInECX: Cardinal; out AReg_EAX, AReg_EBX, AReg_ECX, AReg_EDX); stdcall;
asm
{$IFDEF WIN64}
// save context
PUSH RBX
// CPUID
MOV EAX, AInEAX //Generic function
MOV ECX, AInECX //Generic sub function
//
//For CPU VENDOR STRING EAX := $0
//ECX is not used when EAX = $0
//
//For CPU Extension EAX := $01
//ECX is not used when EAX = $01
//
//For CPU New Extension EAX := $07
//ECX should be $00 to read if RDSEED is available
//
CPUID
// store results
MOV R8, AReg_EAX
MOV R9, AReg_EBX
MOV R10, AReg_ECX
MOV R11, Reg_EDX
MOV Cardinal PTR [R8], EAX
MOV Cardinal PTR [R9], EBX
MOV Cardinal PTR [R10], ECX
MOV Cardinal PTR [R11], EDX
// restore context
POP RBX
{$ELSE}
// save context
PUSH EDI
PUSH EBX
// CPUID
MOV EAX, InEAX //Generic function
MOV ECX, InECX //Generic sub function
//
//For CPU VENDOR STRING EAX := $0
//ECX is not used when EAX = $0
//
//For CPU Extension EAX := $01
//ECX is not used when EAX = $01
//
//For CPU New Extension EAX := $07
//ECX should be $00 to read if RDSEED is available
//
CPUID
// store results
MOV EDI, AReg_EAX
MOV Cardinal PTR [EDI], EAX
MOV EAX, AReg_EBX
MOV EDI, AReg_ECX
MOV Cardinal PTR [EAX], EBX
MOV Cardinal PTR [EDI], ECX
MOV EAX, AReg_EDX
MOV Cardinal PTR [EAX], EDX
// restore context
POP EBX
POP EDI
{$ENDIF}
end;
// Function called from Initialization
function CheckRDInstructions: TRDRANDAvailable;
var
LVendorId: array [0..11] of AnsiChar;
LHighValBase: Cardinal;
LHighValExt1: Cardinal;
LVersionInfo: Cardinal;
LAdditionalInfo: Cardinal;
LExFeatures: Cardinal;
LStdFeatures: Cardinal;
LUnUsed1: Cardinal;
LUnUsed2: Cardinal;
LNewFeatures: Cardinal;
begin
Result.RDRAND := False;
Result.RDSEED := False;
//Check if CPUID istruction is valid testing the bit 21 of EFLAGS
if IsCPUIDValid then
begin
//Get the Vendor string with EAX = 0 and ECX = 0
CPUIDGeneralCall(0, 0, LHighValBase, LVendorId[0], LVendorId[8], LVendorId[4]);
//Verifiy that we are on CPU that we support
if (LVendorId = VendorIDxIntel) or (LVendorId = VendorIDxAMD) then
begin
//Now check if RDRAND and RDSEED is supported inside the extended CPUID flags
if LHighValBase >= 1 then //Supports extensions
begin
//With EAX = 1 AND ECX = 0 the Extension and the available of RDRAND can be read
CPUIDGeneralCall(1, 0, LVersionInfo, LAdditionalInfo, LExFeatures, LStdFeatures);
//ExFeatures (ECX register) bit 30 is 1 if RDRAND is available
if (LExFeatures and ($1 shl 30)) <> 0 then
Result.RDRAND := True;
if LHighValBase >= 7 then
begin
//With EAX = 7 AND ECX = 0 the NEW Extension and the available of RDSEED can be read
CPUIDGeneralCall(7, 0, LHighValExt1, LNewFeatures, LUnUsed1, LUnUsed2);
//New Features (EBX register) bit 18 is 1 if RDSEED is available
if (LNewFeatures and ($1 shl 18)) <> 0 then
Result.RDSEED := True;
end;
end;
end;
end;
end;
initialization
RDInstructionsAvailable := CheckRDInstructions;
end.