VirtualBox

source: vbox/trunk/src/VBox/Runtime/r0drv/nt/initterm-r0drv-nt.cpp@ 62448

Last change on this file since 62448 was 60771, checked in by vboxsync, 9 years ago

RTMpPoke/nt: Removed all the HalSendSoftwareInterrupt code, don't want to spend time trying to figure out why it doesn't work. Just use HalRequestIpi, then no 32/64 differences either.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 18.2 KB
Line 
1/* $Id: initterm-r0drv-nt.cpp 60771 2016-04-29 20:49:59Z vboxsync $ */
2/** @file
3 * IPRT - Initialization & Termination, R0 Driver, NT.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include "the-nt-kernel.h"
32#include <iprt/asm-amd64-x86.h>
33#include <iprt/assert.h>
34#include <iprt/err.h>
35#include <iprt/mp.h>
36#include <iprt/string.h>
37#include "internal/initterm.h"
38#include "internal-r0drv-nt.h"
39#include "symdb.h"
40#include "symdbdata.h"
41
42
43/*********************************************************************************************************************************
44* Global Variables *
45*********************************************************************************************************************************/
46/** The NT CPU set.
47 * KeQueryActiveProcssors() cannot be called at all IRQLs and therefore we'll
48 * have to cache it. Fortunately, Nt doesn't really support taking CPUs offline
49 * or online. It's first with W2K8 that support for CPU hotplugging was added.
50 * Once we start caring about this, we'll simply let the native MP event callback
51 * and update this variable as CPUs comes online. (The code is done already.)
52 */
53RTCPUSET g_rtMpNtCpuSet;
54
55/** ExSetTimerResolution, introduced in W2K. */
56PFNMYEXSETTIMERRESOLUTION g_pfnrtNtExSetTimerResolution;
57/** KeFlushQueuedDpcs, introduced in XP. */
58PFNMYKEFLUSHQUEUEDDPCS g_pfnrtNtKeFlushQueuedDpcs;
59/** HalRequestIpi, version introduced with windows 7. */
60PFNHALREQUESTIPI_W7PLUS g_pfnrtHalRequestIpiW7Plus;
61/** HalRequestIpi, version valid up to windows vista?? */
62PFNHALREQUESTIPI_PRE_W7 g_pfnrtHalRequestIpiPreW7;
63/** Worker for RTMpPokeCpu. */
64PFNRTSENDIPI g_pfnrtMpPokeCpuWorker;
65/** KeIpiGenericCall - Introduced in Windows Server 2003. */
66PFNRTKEIPIGENERICCALL g_pfnrtKeIpiGenericCall;
67/** KeInitializeAffinityEx - Introducted in Windows 7. */
68PFNKEINITIALIZEAFFINITYEX g_pfnrtKeInitializeAffinityEx;
69/** KeAddProcessorAffinityEx - Introducted in Windows 7. */
70PFNKEADDPROCESSORAFFINITYEX g_pfnrtKeAddProcessorAffinityEx;
71/** KeGetProcessorIndexFromNumber - Introducted in Windows 7. */
72PFNKEGETPROCESSORINDEXFROMNUMBER g_pfnrtKeGetProcessorIndexFromNumber;
73/** RtlGetVersion, introduced in ??. */
74PFNRTRTLGETVERSION g_pfnrtRtlGetVersion;
75#ifndef RT_ARCH_AMD64
76/** KeQueryInterruptTime - exported/new in Windows 2000. */
77PFNRTKEQUERYINTERRUPTTIME g_pfnrtKeQueryInterruptTime;
78/** KeQuerySystemTime - exported/new in Windows 2000. */
79PFNRTKEQUERYSYSTEMTIME g_pfnrtKeQuerySystemTime;
80#endif
81/** KeQueryInterruptTimePrecise - new in Windows 8. */
82PFNRTKEQUERYINTERRUPTTIMEPRECISE g_pfnrtKeQueryInterruptTimePrecise;
83/** KeQuerySystemTimePrecise - new in Windows 8. */
84PFNRTKEQUERYSYSTEMTIMEPRECISE g_pfnrtKeQuerySystemTimePrecise;
85
86/** Offset of the _KPRCB::QuantumEnd field. 0 if not found. */
87uint32_t g_offrtNtPbQuantumEnd;
88/** Size of the _KPRCB::QuantumEnd field. 0 if not found. */
89uint32_t g_cbrtNtPbQuantumEnd;
90/** Offset of the _KPRCB::DpcQueueDepth field. 0 if not found. */
91uint32_t g_offrtNtPbDpcQueueDepth;
92
93
94/**
95 * Determines the NT kernel verison information.
96 *
97 * @param pOsVerInfo Where to return the version information.
98 *
99 * @remarks pOsVerInfo->fSmp is only definitive if @c true.
100 * @remarks pOsVerInfo->uCsdNo is set to MY_NIL_CSD if it cannot be determined.
101 */
102static void rtR0NtGetOsVersionInfo(PRTNTSDBOSVER pOsVerInfo)
103{
104 ULONG ulMajorVersion = 0;
105 ULONG ulMinorVersion = 0;
106 ULONG ulBuildNumber = 0;
107
108 pOsVerInfo->fChecked = PsGetVersion(&ulMajorVersion, &ulMinorVersion, &ulBuildNumber, NULL) == TRUE;
109 pOsVerInfo->uMajorVer = (uint8_t)ulMajorVersion;
110 pOsVerInfo->uMinorVer = (uint8_t)ulMinorVersion;
111 pOsVerInfo->uBuildNo = ulBuildNumber;
112#define MY_NIL_CSD 0x3f
113 pOsVerInfo->uCsdNo = MY_NIL_CSD;
114
115 if (g_pfnrtRtlGetVersion)
116 {
117 RTL_OSVERSIONINFOEXW VerInfo;
118 RT_ZERO(VerInfo);
119 VerInfo.dwOSVersionInfoSize = sizeof(VerInfo);
120
121 NTSTATUS rcNt = g_pfnrtRtlGetVersion(&VerInfo);
122 if (NT_SUCCESS(rcNt))
123 pOsVerInfo->uCsdNo = VerInfo.wServicePackMajor;
124 }
125
126 /* Note! We cannot quite say if something is MP or UNI. So, fSmp is
127 redefined to indicate that it must be MP. */
128 pOsVerInfo->fSmp = RTMpGetCount() > 1
129 || ulMajorVersion >= 6; /* Vista and later has no UNI kernel AFAIK. */
130}
131
132
133/**
134 * Tries a set against the current kernel.
135 *
136 * @retval true if it matched up, global variables are updated.
137 * @retval false otherwise (no globals updated).
138 * @param pSet The data set.
139 * @param pbPrcb Pointer to the processor control block.
140 * @param pszVendor Pointer to the processor vendor string.
141 * @param pOsVerInfo The OS version info.
142 */
143static bool rtR0NtTryMatchSymSet(PCRTNTSDBSET pSet, uint8_t *pbPrcb, const char *pszVendor, PCRTNTSDBOSVER pOsVerInfo)
144{
145 /*
146 * Don't bother trying stuff where the NT kernel version number differs, or
147 * if the build type or SMPness doesn't match up.
148 */
149 if ( pSet->OsVerInfo.uMajorVer != pOsVerInfo->uMajorVer
150 || pSet->OsVerInfo.uMinorVer != pOsVerInfo->uMinorVer
151 || pSet->OsVerInfo.fChecked != pOsVerInfo->fChecked
152 || (!pSet->OsVerInfo.fSmp && pOsVerInfo->fSmp /*must-be-smp*/) )
153 {
154 //DbgPrint("IPRT: #%d Version/type mismatch.\n", pSet - &g_artNtSdbSets[0]);
155 return false;
156 }
157
158 /*
159 * Do the CPU vendor test.
160 *
161 * Note! The MmIsAddressValid call is the real #PF security here as the
162 * __try/__except has limited/no ability to catch everything we need.
163 */
164 char *pszPrcbVendorString = (char *)&pbPrcb[pSet->KPRCB.offVendorString];
165 if (!MmIsAddressValid(&pszPrcbVendorString[4 * 3 - 1]))
166 {
167 //DbgPrint("IPRT: #%d invalid vendor string address.\n", pSet - &g_artNtSdbSets[0]);
168 return false;
169 }
170 __try
171 {
172 if (memcmp(pszPrcbVendorString, pszVendor, RT_MIN(4 * 3, pSet->KPRCB.cbVendorString)) != 0)
173 {
174 //DbgPrint("IPRT: #%d Vendor string mismatch.\n", pSet - &g_artNtSdbSets[0]);
175 return false;
176 }
177 }
178 __except(EXCEPTION_EXECUTE_HANDLER)
179 {
180 DbgPrint("IPRT: %#d Exception\n", pSet - &g_artNtSdbSets[0]);
181 return false;
182 }
183
184 /*
185 * Got a match, update the global variables and report succcess.
186 */
187 g_offrtNtPbQuantumEnd = pSet->KPRCB.offQuantumEnd;
188 g_cbrtNtPbQuantumEnd = pSet->KPRCB.cbQuantumEnd;
189 g_offrtNtPbDpcQueueDepth = pSet->KPRCB.offDpcQueueDepth;
190
191#if 0
192 DbgPrint("IPRT: Using data set #%u for %u.%usp%u build %u %s %s.\n",
193 pSet - &g_artNtSdbSets[0],
194 pSet->OsVerInfo.uMajorVer,
195 pSet->OsVerInfo.uMinorVer,
196 pSet->OsVerInfo.uCsdNo,
197 pSet->OsVerInfo.uBuildNo,
198 pSet->OsVerInfo.fSmp ? "smp" : "uni",
199 pSet->OsVerInfo.fChecked ? "checked" : "free");
200#endif
201 return true;
202}
203
204
205DECLHIDDEN(int) rtR0InitNative(void)
206{
207 /*
208 * Init the Nt cpu set.
209 */
210#ifdef IPRT_TARGET_NT4
211 KAFFINITY ActiveProcessors = (UINT64_C(1) << KeNumberProcessors) - UINT64_C(1);
212#else
213 KAFFINITY ActiveProcessors = KeQueryActiveProcessors();
214#endif
215 RTCpuSetEmpty(&g_rtMpNtCpuSet);
216 RTCpuSetFromU64(&g_rtMpNtCpuSet, ActiveProcessors);
217/** @todo Port to W2K8 with > 64 cpus/threads. */
218
219 /*
220 * Initialize the function pointers.
221 */
222#ifdef IPRT_TARGET_NT4
223 g_pfnrtNtExSetTimerResolution = NULL;
224 g_pfnrtNtKeFlushQueuedDpcs = NULL;
225 g_pfnrtHalRequestIpiW7Plus = NULL;
226 g_pfnrtHalRequestIpiPreW7 = NULL;
227 g_pfnrtKeIpiGenericCall = NULL;
228 g_pfnrtKeInitializeAffinityEx = NULL;
229 g_pfnrtKeAddProcessorAffinityEx = NULL;
230 g_pfnrtKeGetProcessorIndexFromNumber = NULL;
231 g_pfnrtRtlGetVersion = NULL;
232 g_pfnrtKeQueryInterruptTime = NULL;
233 g_pfnrtKeQueryInterruptTimePrecise = NULL;
234 g_pfnrtKeQuerySystemTime = NULL;
235 g_pfnrtKeQuerySystemTimePrecise = NULL;
236#else
237 UNICODE_STRING RoutineName;
238 RtlInitUnicodeString(&RoutineName, L"ExSetTimerResolution");
239 g_pfnrtNtExSetTimerResolution = (PFNMYEXSETTIMERRESOLUTION)MmGetSystemRoutineAddress(&RoutineName);
240
241 RtlInitUnicodeString(&RoutineName, L"KeFlushQueuedDpcs");
242 g_pfnrtNtKeFlushQueuedDpcs = (PFNMYKEFLUSHQUEUEDDPCS)MmGetSystemRoutineAddress(&RoutineName);
243
244 RtlInitUnicodeString(&RoutineName, L"HalRequestIpi");
245 g_pfnrtHalRequestIpiW7Plus = (PFNHALREQUESTIPI_W7PLUS)MmGetSystemRoutineAddress(&RoutineName);
246 g_pfnrtHalRequestIpiPreW7 = (PFNHALREQUESTIPI_PRE_W7)g_pfnrtHalRequestIpiW7Plus;
247
248 RtlInitUnicodeString(&RoutineName, L"KeIpiGenericCall");
249 g_pfnrtKeIpiGenericCall = (PFNRTKEIPIGENERICCALL)MmGetSystemRoutineAddress(&RoutineName);
250
251 RtlInitUnicodeString(&RoutineName, L"KeInitializeAffinityEx");
252 g_pfnrtKeInitializeAffinityEx = (PFNKEINITIALIZEAFFINITYEX)MmGetSystemRoutineAddress(&RoutineName);
253
254 RtlInitUnicodeString(&RoutineName, L"KeAddProcessorAffinityEx");
255 g_pfnrtKeAddProcessorAffinityEx = (PFNKEADDPROCESSORAFFINITYEX)MmGetSystemRoutineAddress(&RoutineName);
256
257 RtlInitUnicodeString(&RoutineName, L"KeGetProcessorIndexFromNumber");
258 g_pfnrtKeGetProcessorIndexFromNumber = (PFNKEGETPROCESSORINDEXFROMNUMBER)MmGetSystemRoutineAddress(&RoutineName);
259
260 RtlInitUnicodeString(&RoutineName, L"RtlGetVersion");
261 g_pfnrtRtlGetVersion = (PFNRTRTLGETVERSION)MmGetSystemRoutineAddress(&RoutineName);
262# ifndef RT_ARCH_AMD64
263 RtlInitUnicodeString(&RoutineName, L"KeQueryInterruptTime");
264 g_pfnrtKeQueryInterruptTime = (PFNRTKEQUERYINTERRUPTTIME)MmGetSystemRoutineAddress(&RoutineName);
265
266 RtlInitUnicodeString(&RoutineName, L"KeQuerySystemTime");
267 g_pfnrtKeQuerySystemTime = (PFNRTKEQUERYSYSTEMTIME)MmGetSystemRoutineAddress(&RoutineName);
268# endif
269 RtlInitUnicodeString(&RoutineName, L"KeQueryInterruptTimePrecise");
270 g_pfnrtKeQueryInterruptTimePrecise = (PFNRTKEQUERYINTERRUPTTIMEPRECISE)MmGetSystemRoutineAddress(&RoutineName);
271
272 RtlInitUnicodeString(&RoutineName, L"KeQuerySystemTimePrecise");
273 g_pfnrtKeQuerySystemTimePrecise = (PFNRTKEQUERYSYSTEMTIMEPRECISE)MmGetSystemRoutineAddress(&RoutineName);
274#endif
275
276 /*
277 * HACK ALERT! (and déjà vu warning - remember win32k.sys?)
278 *
279 * Try find _KPRCB::QuantumEnd and _KPRCB::[DpcData.]DpcQueueDepth.
280 * For purpose of verification we use the VendorString member (12+1 chars).
281 *
282 * The offsets was initially derived by poking around with windbg
283 * (dt _KPRCB, !prcb ++, and such like). Systematic harvesting was then
284 * planned using dia2dump, grep and the symbol pack in a manner like this:
285 * dia2dump -type _KDPC_DATA -type _KPRCB EXE\ntkrnlmp.pdb | grep -wE "QuantumEnd|DpcData|DpcQueueDepth|VendorString"
286 *
287 * The final solution ended up using a custom harvester program called
288 * ntBldSymDb that recursively searches thru unpacked symbol packages for
289 * the desired structure offsets. The program assumes that the packages
290 * are unpacked into directories with the same name as the package, with
291 * exception of some of the w2k packages which requires a 'w2k' prefix to
292 * be distinguishable from another.
293 */
294
295 RTNTSDBOSVER OsVerInfo;
296 rtR0NtGetOsVersionInfo(&OsVerInfo);
297
298 /*
299 * Gather consistent CPU vendor string and PRCB pointers.
300 */
301 KIRQL OldIrql;
302 KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); /* make sure we stay on the same cpu */
303
304 union
305 {
306 uint32_t auRegs[4];
307 char szVendor[4*3+1];
308 } u;
309 ASMCpuId(0, &u.auRegs[3], &u.auRegs[0], &u.auRegs[2], &u.auRegs[1]);
310 u.szVendor[4*3] = '\0';
311
312 uint8_t *pbPrcb;
313 __try /* Warning. This try/except statement may provide some false safety. */
314 {
315#if defined(RT_ARCH_X86)
316 PKPCR pPcr = (PKPCR)__readfsdword(RT_OFFSETOF(KPCR,SelfPcr));
317 pbPrcb = (uint8_t *)pPcr->Prcb;
318#elif defined(RT_ARCH_AMD64)
319 PKPCR pPcr = (PKPCR)__readgsqword(RT_OFFSETOF(KPCR,Self));
320 pbPrcb = (uint8_t *)pPcr->CurrentPrcb;
321#else
322# error "port me"
323 pbPrcb = NULL;
324#endif
325 }
326 __except(EXCEPTION_EXECUTE_HANDLER)
327 {
328 pbPrcb = NULL;
329 }
330
331 /*
332 * Search the database
333 */
334 if (pbPrcb)
335 {
336 /* Find the best matching kernel version based on build number. */
337 uint32_t iBest = UINT32_MAX;
338 int32_t iBestDelta = INT32_MAX;
339 for (uint32_t i = 0; i < RT_ELEMENTS(g_artNtSdbSets); i++)
340 {
341 if (g_artNtSdbSets[i].OsVerInfo.fChecked != OsVerInfo.fChecked)
342 continue;
343 if (OsVerInfo.fSmp /*must-be-smp*/ && !g_artNtSdbSets[i].OsVerInfo.fSmp)
344 continue;
345
346 int32_t iDelta = RT_ABS((int32_t)OsVerInfo.uBuildNo - (int32_t)g_artNtSdbSets[i].OsVerInfo.uBuildNo);
347 if ( iDelta == 0
348 && (g_artNtSdbSets[i].OsVerInfo.uCsdNo == OsVerInfo.uCsdNo || OsVerInfo.uCsdNo == MY_NIL_CSD))
349 {
350 /* prefect */
351 iBestDelta = iDelta;
352 iBest = i;
353 break;
354 }
355 if ( iDelta < iBestDelta
356 || iBest == UINT32_MAX
357 || ( iDelta == iBestDelta
358 && OsVerInfo.uCsdNo != MY_NIL_CSD
359 && RT_ABS(g_artNtSdbSets[i ].OsVerInfo.uCsdNo - (int32_t)OsVerInfo.uCsdNo)
360 < RT_ABS(g_artNtSdbSets[iBest].OsVerInfo.uCsdNo - (int32_t)OsVerInfo.uCsdNo)
361 )
362 )
363 {
364 iBestDelta = iDelta;
365 iBest = i;
366 }
367 }
368 if (iBest < RT_ELEMENTS(g_artNtSdbSets))
369 {
370 /* Try all sets: iBest -> End; iBest -> Start. */
371 bool fDone = false;
372 int32_t i = iBest;
373 while ( i < RT_ELEMENTS(g_artNtSdbSets)
374 && !(fDone = rtR0NtTryMatchSymSet(&g_artNtSdbSets[i], pbPrcb, u.szVendor, &OsVerInfo)))
375 i++;
376 if (!fDone)
377 {
378 i = (int32_t)iBest - 1;
379 while ( i >= 0
380 && !(fDone = rtR0NtTryMatchSymSet(&g_artNtSdbSets[i], pbPrcb, u.szVendor, &OsVerInfo)))
381 i--;
382 }
383 }
384 else
385 DbgPrint("IPRT: Failed to locate data set.\n");
386 }
387 else
388 DbgPrint("IPRT: Failed to get PCBR pointer.\n");
389
390 KeLowerIrql(OldIrql); /* Lowering the IRQL early in the hope that we may catch exceptions below. */
391
392#ifndef IN_GUEST
393 if (!g_offrtNtPbQuantumEnd && !g_offrtNtPbDpcQueueDepth)
394 DbgPrint("IPRT: Neither _KPRCB::QuantumEnd nor _KPRCB::DpcQueueDepth was not found! Kernel %u.%u %u %s\n",
395 OsVerInfo.uMajorVer, OsVerInfo.uMinorVer, OsVerInfo.uBuildNo, OsVerInfo.fChecked ? "checked" : "free");
396# ifdef DEBUG
397 else
398 DbgPrint("IPRT: _KPRCB:{.QuantumEnd=%x/%d, .DpcQueueDepth=%x/%d} Kernel %u.%u %u %s\n",
399 g_offrtNtPbQuantumEnd, g_cbrtNtPbQuantumEnd, g_offrtNtPbDpcQueueDepth,
400 OsVerInfo.uMajorVer, OsVerInfo.uMinorVer, OsVerInfo.uBuildNo, OsVerInfo.fChecked ? "checked" : "free");
401# endif
402#endif
403
404 /*
405 * Special IPI fun for RTMpPokeCpu.
406 *
407 * On Vista and later the DPC method doesn't seem to reliably send IPIs,
408 * so we have to use alternative methods.
409 *
410 * On AMD64 We used to use the HalSendSoftwareInterrupt API (also x86 on
411 * W10+), it looks faster and more convenient to use, however we're either
412 * using it wrong or it doesn't reliably do what we want (see @bugref{8343}).
413 *
414 * The HalRequestIpip API is thus far the only alternative to KeInsertQueueDpc
415 * for doing targetted IPIs. Trouble with this API is that it changed
416 * fundamentally in Window 7 when they added support for lots of processors.
417 *
418 * If we really think we cannot use KeInsertQueueDpc, we use the broadcast IPI
419 * API KeIpiGenericCall.
420 */
421 if ( OsVerInfo.uMajorVer > 6
422 || (OsVerInfo.uMajorVer == 6 && OsVerInfo.uMinorVer > 0))
423 g_pfnrtHalRequestIpiPreW7 = NULL;
424 else
425 g_pfnrtHalRequestIpiW7Plus = NULL;
426
427 g_pfnrtMpPokeCpuWorker = rtMpPokeCpuUsingDpc;
428#ifndef IPRT_TARGET_NT4
429 if ( g_pfnrtHalRequestIpiW7Plus
430 && g_pfnrtKeInitializeAffinityEx
431 && g_pfnrtKeAddProcessorAffinityEx
432 && g_pfnrtKeGetProcessorIndexFromNumber)
433 {
434 DbgPrint("IPRT: RTMpPoke => rtMpPokeCpuUsingHalReqestIpiW7Plus\n");
435 g_pfnrtMpPokeCpuWorker = rtMpPokeCpuUsingHalReqestIpiW7Plus;
436 }
437 else if (OsVerInfo.uMajorVer >= 6 && g_pfnrtKeIpiGenericCall)
438 {
439 DbgPrint("IPRT: RTMpPoke => rtMpPokeCpuUsingBroadcastIpi\n");
440 g_pfnrtMpPokeCpuWorker = rtMpPokeCpuUsingBroadcastIpi;
441 }
442 else
443 DbgPrint("IPRT: RTMpPoke => rtMpPokeCpuUsingDpc\n");
444 /* else: Windows XP should send always send an IPI -> VERIFY */
445#endif
446
447 return VINF_SUCCESS;
448}
449
450
451DECLHIDDEN(void) rtR0TermNative(void)
452{
453}
454
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette