with this very last article, I would like to inform you that this blog will be closed at the end of June. This decision was made for two reasons. (1) The Windows Research Kernel was a great step from Microsoft towards academia. Together with its license the WRK enabled universities to use the Windows source code for their teaching efforts. Unfortunately, there is no news about next versions of the WRK, which makes it hard to study and teach new trends in hardware design, such as multi-core architectures, hybrid systems, and memory hierarchies, in the context of the Windows operating system and to share those insights via this blog. (2) With the submission of my PhD thesis, my affiliation with the HPI will come to a close at the end of the year.
Nevertheless, we are going to maintain a static version of all the blog posts submitted so far. We will, however, disable comments for individual blogs. The transition to the static version should be completed by the end of June. In case you encounter any broken links, even past the migration time, please let us know via email.
Cheers,
Alex
The only GUI Hyper-V provides for importing a VM from a template is as follows:
The GUI dialog does not provide any options for specifying a different import location. If you import a virtual machine through that GUI, Hyper-V will attempt to import the VHD file into its base directory (usually \Users\Public\Public Documents\Hyper-V\Virtual hard disks). This behavior leads to the file collision mentioned above. Looking for another alternative, I stumbled upon the PowerShell Management Library for Hyper-V (PsHyperV), a cool and rich PowerShell scripting library for Hyper-V.
PsHyperV provides the Import-VM commandlet, which allows you to import a virtual machine programmatically via PowerShell. Unfortunately, it only provides the same amount of paramters as the GUI version and thus fails to import a second instance of the same virtual machine, since it does not allow you to specify a location to which the VM should be imported to. However, the source code of the commandlet was a good starting point for learning about WMI management classes for Hyper-V.
Digging into the documentation of Hyper-V’s WMI classes, I found the following method, which is available since Server 2008 R2:
uint32 ImportVirtualSystemEx( [in] string ImportDirectory, [in] string ImportSettingData, [out] CIM_ConcreteJob REF Job );
This method allows you to import a virtual computer system from a specified folder (ImportDirectory) and to specify import settings (ImportSettingData). The ImportSettingData parameter, prior to serialization, has the following type:
class Msvm_VirtualSystemImportSettingData : CIM_SettingData { string Description; string Caption; string InstanceID; string ElementName; boolean GenerateNewId; boolean CreateCopy; string Name; string SourceVmDataRoot; string SourceSnapshotDataRoot; string SourceVhdDataRoot; string TargetVmDataRoot; string TargetSnapshotDataRoot; string TargetVhdDataRoot; string SecurityScope; string CurrentResourcePaths[]; string SourceResourcePaths[]; string TargetResourcePaths[]; string SourceNetworkConnections[]; string TargetNetworkConnections[]; };
The properties TargetVmDataRoot, TargetSnapshotDataRoot, and TargetVhdDataRoot allow you to specify a directory for where the respective information is stored. So, back to my original problem: I ended up writing a small PowerShell script that creates a directory for each instance that should be imported and then points the Target* properties to that location. Finally the script calls the ImportVirtualSystemEx method to import the VM. It takes quite a while for importing one single instance, but at the end all 30 instances were imported successfully. Here is my script:
$exportDir = "C:\Exports\InstantLab Test Instance Debugger" $cloneRoot = "C:\Users\Public\Documents\Hyper-V\Clones" $i = 0 # Load management class $managementService = Get-WmiObject -Namespace "root\virtualization" -Class "Msvm_VirtualSystemManagementService" $importSettings = $managementService.GetVirtualSystemImportSettingData($exportDir) $importSettings = $importSettings.ImportSettingData # set global import settings $importSettings.GenerateNewId = $true $importSettings.CreateCopy = $true # now do the following in a loop per instance while ($i -le 30) { echo "Creating debugger instance $i" $importDir = "$cloneRoot\Debugger$i" echo " Making import directory: $importDir" mkdir $importDir echo " Updating import settings" $importSettings.Name = "InstantLab Debugger $i" $importSettings.TargetVhdDataRoot = $importDir $importSettings.TargetSnapshotDataRoot = $importDir $importSettings.TargetVmDataRoot = $importDir $result = $managementService.ImportVirtualSystemEx($exportDir, $importSettings.GetText(1)) # wait for the job to be finished Test-WMIJob $result.Job -wait -StatusOnly -Description " Importing VM" $i += 1 echo "Done." } echo "Done."
The Application Binary Interface for the WRK is determined through the system call table and its calling conventions (system call number in EAX, parameters on the stack). Since I don’t assume that the calling conventions have changed, I concentrated on the system call table. If any of the system call numbers would differ, both versions are incompatible. On the Metasploit web site, you can find a comparison of system calls from various Windows versions. Unfortunately, system calls of Server 2003 SP2 are not listed in their table. It also appears that the site is not active anymore since the comparison ends with Windows Vista.
So, I had to find out the system call table myself. Fortunately, WinDbg is your friend in such a case. The user mode portion of a Windows system call is implemented in ntdll.dll and looks like this:
mov eax,<system_call_number> mov edx,offset SharedUserData!SystemCallStub (<resolved_address>) call dword ptr [edx] ret 8
Since system calls are exported by ntdll.dll, we can scan through all the exported symbols starting with Nt. Then we can disassemble the instructions for each function. As shown in the snippet above, the first two instructions are enough to figure out, whether the symbol we found is actually a system call. To retrieve a disassembly of all exported symbols starting with Nt, I used the following WinDbg command:
.foreach /ps 5 (syscall {x ntdll!Nt*}) {u syscall L2}
The command x ntdll!Nt* examines all symbols from ntdll.dll that start with Nt. Each line is passed to a variable named syscall, for which we disassemble (u) the first two instructions (L2). Unfortunately, the .foreach command does not iterate line wise over an input stream but rather word wise. Since the x command delivers multiple words of information per symbol, the /ps switch tells .foreach to skip 5 of those (unnecessary) parameters. Finally, I wrote a small Python script to search through the generated output to identify all the included system calls and extract the system call number.
I performed this experiment twice: once on an SP1 and once on a SP2 machine. Here are the system call tables of these two versions:
| Number | Service Pack 1 | Service Pack 2 |
|---|---|---|
| 0 | NtAcceptConnectPort | NtAcceptConnectPort |
| 1 | NtAccessCheck | NtAccessCheck |
| 2 | NtAccessCheckAndAuditAlarm | NtAccessCheckAndAuditAlarm |
| 3 | NtAccessCheckByType | NtAccessCheckByType |
| 4 | NtAccessCheckByTypeAndAuditAlarm | NtAccessCheckByTypeAndAuditAlarm |
| 5 | NtAccessCheckByTypeResultList | NtAccessCheckByTypeResultList |
| 6 | NtAccessCheckByTypeResultListAnd-AuditAlarm | NtAccessCheckByTypeResultListAnd-AuditAlarm |
| 7 | NtAccessCheckByTypeResultListAnd-AuditAlarmByHandle | NtAccessCheckByTypeResultListAnd-AuditAlarmByHandle |
| 8 | NtAddAtom | NtAddAtom |
| 9 | NtAddBootEntry | NtAddBootEntry |
| 10 | NtAddDriverEntry | NtAddDriverEntry |
| 11 | NtAdjustGroupsToken | NtAdjustGroupsToken |
| 12 | NtAdjustPrivilegesToken | NtAdjustPrivilegesToken |
| 13 | NtAlertResumeThread | NtAlertResumeThread |
| 14 | NtAlertThread | NtAlertThread |
| 15 | NtAllocateLocallyUniqueId | NtAllocateLocallyUniqueId |
| 16 | NtAllocateUserPhysicalPages | NtAllocateUserPhysicalPages |
| 17 | NtAllocateUuids | NtAllocateUuids |
| 18 | NtAllocateVirtualMemory | NtAllocateVirtualMemory |
| 19 | NtApphelpCacheControl | NtApphelpCacheControl |
| 20 | NtAreMappedFilesTheSame | NtAreMappedFilesTheSame |
| 21 | NtAssignProcessToJobObject | NtAssignProcessToJobObject |
| 22 | NtCallbackReturn | NtCallbackReturn |
| 23 | NtCancelDeviceWakeupRequest | NtCancelDeviceWakeupRequest |
| 24 | NtCancelIoFile | NtCancelIoFile |
| 25 | NtCancelTimer | NtCancelTimer |
| 26 | NtClearEvent | NtClearEvent |
| 27 | NtClose | NtClose |
| 28 | NtCloseObjectAuditAlarm | NtCloseObjectAuditAlarm |
| 29 | NtCompactKeys | NtCompactKeys |
| 30 | NtCompareTokens | NtCompareTokens |
| 31 | NtCompleteConnectPort | NtCompleteConnectPort |
| 32 | NtCompressKey | NtCompressKey |
| 33 | NtConnectPort | NtConnectPort |
| 34 | NtContinue | NtContinue |
| 35 | NtCreateDebugObject | NtCreateDebugObject |
| 36 | NtCreateDirectoryObject | NtCreateDirectoryObject |
| 37 | NtCreateEvent | NtCreateEvent |
| 38 | NtCreateEventPair | NtCreateEventPair |
| 39 | NtCreateFile | NtCreateFile |
| 40 | NtCreateIoCompletion | NtCreateIoCompletion |
| 41 | NtCreateJobObject | NtCreateJobObject |
| 42 | NtCreateJobSet | NtCreateJobSet |
| 43 | NtCreateKey | NtCreateKey |
| 44 | NtCreateMailslotFile | NtCreateMailslotFile |
| 45 | NtCreateMutant | NtCreateMutant |
| 46 | NtCreateNamedPipeFile | NtCreateNamedPipeFile |
| 47 | NtCreatePagingFile | NtCreatePagingFile |
| 48 | NtCreatePort | NtCreatePort |
| 49 | NtCreateProcess | NtCreateProcess |
| 50 | NtCreateProcessEx | NtCreateProcessEx |
| 51 | NtCreateProfile | NtCreateProfile |
| 52 | NtCreateSection | NtCreateSection |
| 53 | NtCreateSemaphore | NtCreateSemaphore |
| 54 | NtCreateSymbolicLinkObject | NtCreateSymbolicLinkObject |
| 55 | NtCreateThread | NtCreateThread |
| 56 | NtCreateTimer | NtCreateTimer |
| 57 | NtCreateToken | NtCreateToken |
| 58 | NtCreateWaitablePort | NtCreateWaitablePort |
| 59 | NtDebugActiveProcess | NtDebugActiveProcess |
| 60 | NtDebugContinue | NtDebugContinue |
| 61 | NtDelayExecution | NtDelayExecution |
| 62 | NtDeleteAtom | NtDeleteAtom |
| 63 | NtDeleteBootEntry | NtDeleteBootEntry |
| 64 | NtDeleteDriverEntry | NtDeleteDriverEntry |
| 65 | NtDeleteFile | NtDeleteFile |
| 66 | NtDeleteKey | NtDeleteKey |
| 67 | NtDeleteObjectAuditAlarm | NtDeleteObjectAuditAlarm |
| 68 | NtDeleteValueKey | NtDeleteValueKey |
| 69 | NtDeviceIoControlFile | NtDeviceIoControlFile |
| 70 | NtDisplayString | NtDisplayString |
| 71 | NtDuplicateObject | NtDuplicateObject |
| 72 | NtDuplicateToken | NtDuplicateToken |
| 73 | NtEnumerateBootEntries | NtEnumerateBootEntries |
| 74 | NtEnumerateDriverEntries | NtEnumerateDriverEntries |
| 75 | NtEnumerateKey | NtEnumerateKey |
| 76 | NtEnumerateSystemEnvironment-ValuesEx | NtEnumerateSystemEnvironment-ValuesEx |
| 77 | NtEnumerateValueKey | NtEnumerateValueKey |
| 78 | NtExtendSection | NtExtendSection |
| 79 | NtFilterToken | NtFilterToken |
| 80 | NtFindAtom | NtFindAtom |
| 81 | NtFlushBuffersFile | NtFlushBuffersFile |
| 82 | NtFlushInstructionCache | NtFlushInstructionCache |
| 83 | NtFlushKey | NtFlushKey |
| 84 | NtFlushVirtualMemory | NtFlushVirtualMemory |
| 85 | NtFlushWriteBuffer | NtFlushWriteBuffer |
| 86 | NtFreeUserPhysicalPages | NtFreeUserPhysicalPages |
| 87 | NtFreeVirtualMemory | NtFreeVirtualMemory |
| 88 | NtFsControlFile | NtFsControlFile |
| 89 | NtGetContextThread | NtGetContextThread |
| 90 | NtGetDevicePowerState | NtGetDevicePowerState |
| 91 | NtGetPlugPlayEvent | NtGetPlugPlayEvent |
| 92 | NtGetWriteWatch | NtGetWriteWatch |
| 93 | NtImpersonateAnonymousToken | NtImpersonateAnonymousToken |
| 94 | NtImpersonateClientOfPort | NtImpersonateClientOfPort |
| 95 | NtImpersonateThread | NtImpersonateThread |
| 96 | NtInitializeRegistry | NtInitializeRegistry |
| 97 | NtInitiatePowerAction | NtInitiatePowerAction |
| 98 | NtIsProcessInJob | NtIsProcessInJob |
| 99 | NtIsSystemResumeAutomatic | NtIsSystemResumeAutomatic |
| 100 | NtListenPort | NtListenPort |
| 101 | NtLoadDriver | NtLoadDriver |
| 102 | NtLoadKey | NtLoadKey |
| 103 | NtLoadKey2 | NtLoadKey2 |
| 104 | NtLoadKeyEx | NtLoadKeyEx |
| 105 | NtLockFile | NtLockFile |
| 106 | NtLockProductActivationKeys | NtLockProductActivationKeys |
| 107 | NtLockRegistryKey | NtLockRegistryKey |
| 108 | NtLockVirtualMemory | NtLockVirtualMemory |
| 109 | NtMakePermanentObject | NtMakePermanentObject |
| 110 | NtMakeTemporaryObject | NtMakeTemporaryObject |
| 111 | NtMapUserPhysicalPages | NtMapUserPhysicalPages |
| 112 | NtMapUserPhysicalPagesScatter | NtMapUserPhysicalPagesScatter |
| 113 | NtMapViewOfSection | NtMapViewOfSection |
| 114 | NtModifyBootEntry | NtModifyBootEntry |
| 115 | NtModifyDriverEntry | NtModifyDriverEntry |
| 116 | NtNotifyChangeDirectoryFile | NtNotifyChangeDirectoryFile |
| 117 | NtNotifyChangeKey | NtNotifyChangeKey |
| 118 | NtNotifyChangeMultipleKeys | NtNotifyChangeMultipleKeys |
| 119 | NtOpenDirectoryObject | NtOpenDirectoryObject |
| 120 | NtOpenEvent | NtOpenEvent |
| 121 | NtOpenEventPair | NtOpenEventPair |
| 122 | NtOpenFile | NtOpenFile |
| 123 | NtOpenIoCompletion | NtOpenIoCompletion |
| 124 | NtOpenJobObject | NtOpenJobObject |
| 125 | NtOpenKey | NtOpenKey |
| 126 | NtOpenMutant | NtOpenMutant |
| 127 | NtOpenObjectAuditAlarm | NtOpenObjectAuditAlarm |
| 128 | NtOpenProcess | NtOpenProcess |
| 129 | NtOpenProcessToken | NtOpenProcessToken |
| 130 | NtOpenProcessTokenEx | NtOpenProcessTokenEx |
| 131 | NtOpenSection | NtOpenSection |
| 132 | NtOpenSemaphore | NtOpenSemaphore |
| 133 | NtOpenSymbolicLinkObject | NtOpenSymbolicLinkObject |
| 134 | NtOpenThread | NtOpenThread |
| 135 | NtOpenThreadToken | NtOpenThreadToken |
| 136 | NtOpenThreadTokenEx | NtOpenThreadTokenEx |
| 137 | NtOpenTimer | NtOpenTimer |
| 138 | NtPlugPlayControl | NtPlugPlayControl |
| 139 | NtPowerInformation | NtPowerInformation |
| 140 | NtPrivilegeCheck | NtPrivilegeCheck |
| 141 | NtPrivilegeObjectAuditAlarm | NtPrivilegeObjectAuditAlarm |
| 142 | NtPrivilegedServiceAuditAlarm | NtPrivilegedServiceAuditAlarm |
| 143 | NtProtectVirtualMemory | NtProtectVirtualMemory |
| 144 | NtPulseEvent | NtPulseEvent |
| 145 | NtQueryAttributesFile | NtQueryAttributesFile |
| 146 | NtQueryBootEntryOrder | NtQueryBootEntryOrder |
| 147 | NtQueryBootOptions | NtQueryBootOptions |
| 148 | NtQueryDebugFilterState | NtQueryDebugFilterState |
| 149 | NtQueryDefaultLocale | NtQueryDefaultLocale |
| 150 | NtQueryDefaultUILanguage | NtQueryDefaultUILanguage |
| 151 | NtQueryDirectoryFile | NtQueryDirectoryFile |
| 152 | NtQueryDirectoryObject | NtQueryDirectoryObject |
| 153 | NtQueryDriverEntryOrder | NtQueryDriverEntryOrder |
| 154 | NtQueryEaFile | NtQueryEaFile |
| 155 | NtQueryEvent | NtQueryEvent |
| 156 | NtQueryFullAttributesFile | NtQueryFullAttributesFile |
| 157 | NtQueryInformationAtom | NtQueryInformationAtom |
| 158 | NtQueryInformationFile | NtQueryInformationFile |
| 159 | NtQueryInformationJobObject | NtQueryInformationJobObject |
| 160 | NtQueryInformationPort | NtQueryInformationPort |
| 161 | NtQueryInformationProcess | NtQueryInformationProcess |
| 162 | NtQueryInformationThread | NtQueryInformationThread |
| 163 | NtQueryInformationToken | NtQueryInformationToken |
| 164 | NtQueryInstallUILanguage | NtQueryInstallUILanguage |
| 165 | NtQueryIntervalProfile | NtQueryIntervalProfile |
| 166 | NtQueryIoCompletion | NtQueryIoCompletion |
| 167 | NtQueryKey | NtQueryKey |
| 168 | NtQueryMultipleValueKey | NtQueryMultipleValueKey |
| 169 | NtQueryMutant | NtQueryMutant |
| 170 | NtQueryObject | NtQueryObject |
| 171 | NtQueryOpenSubKeys | NtQueryOpenSubKeys |
| 172 | NtQueryOpenSubKeysEx | NtQueryOpenSubKeysEx |
| 173 | NtQueryPerformanceCounter | NtQueryPerformanceCounter |
| 174 | NtQueryQuotaInformationFile | NtQueryQuotaInformationFile |
| 175 | NtQuerySection | NtQuerySection |
| 176 | NtQuerySecurityObject | NtQuerySecurityObject |
| 177 | NtQuerySemaphore | NtQuerySemaphore |
| 178 | NtQuerySymbolicLinkObject | NtQuerySymbolicLinkObject |
| 179 | NtQuerySystemEnvironmentValue | NtQuerySystemEnvironmentValue |
| 180 | NtQuerySystemEnvironmentValueEx | NtQuerySystemEnvironmentValueEx |
| 181 | NtQuerySystemInformation | NtQuerySystemInformation |
| 182 | NtQuerySystemTime | NtQuerySystemTime |
| 183 | NtQueryTimer | NtQueryTimer |
| 184 | NtQueryTimerResolution | NtQueryTimerResolution |
| 185 | NtQueryValueKey | NtQueryValueKey |
| 186 | NtQueryVirtualMemory | NtQueryVirtualMemory |
| 187 | NtQueryVolumeInformationFile | NtQueryVolumeInformationFile |
| 188 | NtQueueApcThread | NtQueueApcThread |
| 189 | NtRaiseException | NtRaiseException |
| 190 | NtRaiseHardError | NtRaiseHardError |
| 191 | NtReadFile | NtReadFile |
| 192 | NtReadFileScatter | NtReadFileScatter |
| 193 | NtReadRequestData | NtReadRequestData |
| 194 | NtReadVirtualMemory | NtReadVirtualMemory |
| 195 | NtRegisterThreadTerminatePort | NtRegisterThreadTerminatePort |
| 196 | NtReleaseMutant | NtReleaseMutant |
| 197 | NtReleaseSemaphore | NtReleaseSemaphore |
| 198 | NtRemoveIoCompletion | NtRemoveIoCompletion |
| 199 | NtRemoveProcessDebug | NtRemoveProcessDebug |
| 200 | NtRenameKey | NtRenameKey |
| 201 | NtReplaceKey | NtReplaceKey |
| 202 | NtReplyPort | NtReplyPort |
| 203 | NtReplyWaitReceivePort | NtReplyWaitReceivePort |
| 204 | NtReplyWaitReceivePortEx | NtReplyWaitReceivePortEx |
| 205 | NtReplyWaitReplyPort | NtReplyWaitReplyPort |
| 206 | NtRequestDeviceWakeup | NtRequestDeviceWakeup |
| 207 | NtRequestPort | NtRequestPort |
| 208 | NtRequestWaitReplyPort | NtRequestWaitReplyPort |
| 209 | NtRequestWakeupLatency | NtRequestWakeupLatency |
| 210 | NtResetEvent | NtResetEvent |
| 211 | NtResetWriteWatch | NtResetWriteWatch |
| 212 | NtRestoreKey | NtRestoreKey |
| 213 | NtResumeProcess | NtResumeProcess |
| 214 | NtResumeThread | NtResumeThread |
| 215 | NtSaveKey | NtSaveKey |
| 216 | NtSaveKeyEx | NtSaveKeyEx |
| 217 | NtSaveMergedKeys | NtSaveMergedKeys |
| 218 | NtSecureConnectPort | NtSecureConnectPort |
| 219 | NtSetBootEntryOrder | NtSetBootEntryOrder |
| 220 | NtSetBootOptions | NtSetBootOptions |
| 221 | NtSetContextThread | NtSetContextThread |
| 222 | NtSetDebugFilterState | NtSetDebugFilterState |
| 223 | NtSetDefaultHardErrorPort | NtSetDefaultHardErrorPort |
| 224 | NtSetDefaultLocale | NtSetDefaultLocale |
| 225 | NtSetDefaultUILanguage | NtSetDefaultUILanguage |
| 226 | NtSetDriverEntryOrder | NtSetDriverEntryOrder |
| 227 | NtSetEaFile | NtSetEaFile |
| 228 | NtSetEvent | NtSetEvent |
| 229 | NtSetEventBoostPriority | NtSetEventBoostPriority |
| 230 | NtSetHighEventPair | NtSetHighEventPair |
| 231 | NtSetHighWaitLowEventPair | NtSetHighWaitLowEventPair |
| 232 | NtSetInformationDebugObject | NtSetInformationDebugObject |
| 233 | NtSetInformationFile | NtSetInformationFile |
| 234 | NtSetInformationJobObject | NtSetInformationJobObject |
| 235 | NtSetInformationKey | NtSetInformationKey |
| 236 | NtSetInformationObject | NtSetInformationObject |
| 237 | NtSetInformationProcess | NtSetInformationProcess |
| 238 | NtSetInformationThread | NtSetInformationThread |
| 239 | NtSetInformationToken | NtSetInformationToken |
| 240 | NtSetIntervalProfile | NtSetIntervalProfile |
| 241 | NtSetIoCompletion | NtSetIoCompletion |
| 242 | NtSetLdtEntries | NtSetLdtEntries |
| 243 | NtSetLowEventPair | NtSetLowEventPair |
| 244 | NtSetLowWaitHighEventPair | NtSetLowWaitHighEventPair |
| 245 | NtSetQuotaInformationFile | NtSetQuotaInformationFile |
| 246 | NtSetSecurityObject | NtSetSecurityObject |
| 247 | NtSetSystemEnvironmentValue | NtSetSystemEnvironmentValue |
| 248 | NtSetSystemEnvironmentValueEx | NtSetSystemEnvironmentValueEx |
| 249 | NtSetSystemInformation | NtSetSystemInformation |
| 250 | NtSetSystemPowerState | NtSetSystemPowerState |
| 251 | NtSetSystemTime | NtSetSystemTime |
| 252 | NtSetThreadExecutionState | NtSetThreadExecutionState |
| 253 | NtSetTimer | NtSetTimer |
| 254 | NtSetTimerResolution | NtSetTimerResolution |
| 255 | NtSetUuidSeed | NtSetUuidSeed |
| 256 | NtSetValueKey | NtSetValueKey |
| 257 | NtSetVolumeInformationFile | NtSetVolumeInformationFile |
| 258 | NtShutdownSystem | NtShutdownSystem |
| 259 | NtSignalAndWaitForSingleObject | NtSignalAndWaitForSingleObject |
| 260 | NtStartProfile | NtStartProfile |
| 261 | NtStopProfile | NtStopProfile |
| 262 | NtSuspendProcess | NtSuspendProcess |
| 263 | NtSuspendThread | NtSuspendThread |
| 264 | NtSystemDebugControl | NtSystemDebugControl |
| 265 | NtTerminateJobObject | NtTerminateJobObject |
| 266 | NtTerminateProcess | NtTerminateProcess |
| 267 | NtTerminateThread | NtTerminateThread |
| 268 | NtTestAlert | NtTestAlert |
| 269 | NtTraceEvent | NtTraceEvent |
| 270 | NtTranslateFilePath | NtTranslateFilePath |
| 271 | NtUnloadDriver | NtUnloadDriver |
| 272 | NtUnloadKey | NtUnloadKey |
| 273 | NtUnloadKey2 | NtUnloadKey2 |
| 274 | NtUnloadKeyEx | NtUnloadKeyEx |
| 275 | NtUnlockFile | NtUnlockFile |
| 276 | NtUnlockVirtualMemory | NtUnlockVirtualMemory |
| 277 | NtUnmapViewOfSection | NtUnmapViewOfSection |
| 278 | NtVdmControl | NtVdmControl |
| 279 | NtWaitForDebugEvent | NtWaitForDebugEvent |
| 280 | NtWaitForMultipleObjects | NtWaitForMultipleObjects |
| 281 | NtWaitForSingleObject | NtWaitForSingleObject |
| 282 | NtWaitHighEventPair | NtWaitHighEventPair |
| 283 | NtWaitLowEventPair | NtWaitLowEventPair |
| 284 | NtWriteFile | NtWriteFile |
| 285 | NtWriteFileGather | NtWriteFileGather |
| 286 | NtWriteRequestData | NtWriteRequestData |
| 287 | NtWriteVirtualMemory | NtWriteVirtualMemory |
| 288 | NtYieldExecution | NtYieldExecution |
| 289 | NtCreateKeyedEvent | NtCreateKeyedEvent |
| 290 | NtOpenKeyedEvent | NtOpenKeyedEvent |
| 291 | NtReleaseKeyedEvent | NtReleaseKeyedEvent |
| 292 | NtWaitForKeyedEvent | NtWaitForKeyedEvent |
| 293 | NtQueryPortInformationProcess | NtQueryPortInformationProcess |
| 294 | NtGetCurrentProcessorNumber | NtGetCurrentProcessorNumber |
| 295 | NtWaitForMultipleObjects32 | NtWaitForMultipleObjects32 |
As you can see in the table above, both versions offer the same amount of system calls, which is why, from an ABI perspective, the WRK can run on both operating system versions. Unfortunately, there is one uncertainty left: the WRK links against modules such as the HAL. If there are any dependencies broken, which I think is pretty unlikely, you may encounter unexpected crashes or unpredictable behavior.
If you have any comments or hints about how to resolve the issue, please let us know.
]]>In this tutorial, we will not describe how to configure WinDbg or the WRK to communicate via the COM1 port since this is described elsewhere. We only concentrate on the configuration of the named pipe to connect both VMs. You will notice that the following screenshots were taken on a German system installation. However, the menu locations should be the same in your language and we will also describe what is shown in the image.
If you want to configure the serial port in VMware Fusion 3.0, you will only get this dialog:
Which means that the standard configuration for the serial port is used to connect your virtual machine with printers available on Mac OS. The only other option in the GUI you have is to connect the serial port with a file, which is not appropriate for our purpose.
The solution is to manually configure the VM configuration file. Configuration files have the suffix .vmx attached. If you are using a version of VMware Fusion prior to version 3, you will find the configuration file right next to the virtual hard disk file. On Mac OS, virtual machines typically reside in ~/Library/Application Support/VMware Fusion/Virtual Machines/, but you may be using a different location for your VMs. If you are using VMware Fusion 3, VMs and their associated files are organized in packages with the suffix .vmwarevm. To locate the VM configuration file, simply right click the .vmwarevm package in Finder and select Show Packet Contents, as shown in the following figure for the Boot Camp VM.
After locating the .vmx files for the WRK VM and the debugger VM, open each file with a text editor and locate the lines containing serial0. If your VM is configured to connect with printers, you will find the following two lines in your configuration file:
serial0.present = "TRUE" serial0.fileType = "thinprint"
Please note that the contents may differ or that the serial connection may not be present at all.
Since we are using a pipe, we need to define a server, which creates the pipe, and a client, which connects to the pipe. My proposal here is to use the WRK VM as a client and the debugger VM as the server. For example, on my Mac, I am using my Boot Camp partition as debugger VM. To configure the server end of the named pipe, replace all the lines starting with serial0 in your debugger VM configuration file with the following:
serial0.present = "TRUE" serial0.fileType = "pipe" serial0.fileName = "/private/tmp/com1" serial0.tryNoRxLoss = "FALSE" serial0.pipe.endPoint = "server"
This tells VMware that the serial port (serial0) is available (present) and represented by a pipe. The named pipe must be located on the local file system in a directory, for which VMware Fusion has write permissions. My suggestion is to use /tmp/com1 or /private/tmp/com1, since /tmp is only a link. The endpoint property tells VMware that this virtual machine is supposed to create the named pipe.
To configure the client end of the named pipe, replace all the lines starting with serial0 in your WRK VM configuration file with the following:
serial0.present = "TRUE" serial0.fileType = "pipe" serial0.fileName = "/private/tmp/com1" serial0.tryNoRxLoss = "FALSE" serial0.pipe.endPoint = "client"
Again, we are using the named pipe /private/tmp/com1 but this time as a client, which means that VMware expects the named pipe to already being created. Apparently, this specification has implications on the boot order of your virtual machines: since the debugger VM is the server, it must be started prior to the WRK VM.
Now safe the configuration files and close the editor. If you check the virtual machine settings in VMware Fusion for either VM, you will now find the following options dialog for the serial port (which reads: Use user defined, unsupported settings):
If you boot the debugger VM first and then the WRK VM in /debug mode with /debugport=com1, you should be able to connect the kernel debugger with the WRK.
DISCLAIMER
Please note that this description is provided “as is” with absolutely no warranties. Since we are using undocumented properties, the configuration described herein may not be working in future releases of VMware Fusion. If that should be the case or you found out another way of how to connect two VMs with a named pipe in a documented fashion, please feel free to post your experiences here.
]]>In the meantime, we are working on a release of the PXR sources, so that you can start building a documentation of your own projects.
]]>Since the WRK documentation says that the WRK should be deployed on a Windows Server 2003 (W2K3) SP1 system, we were not sure how to proceed. We finally decided to give it a try and the WRK boots in this environment and works—so far. We do not know, if there may be any compatibility issues between the W2K3 SP2 environment and requirements demanded by the WRK. To further investigate the issue, we will run a set of experiments to figure out, if the WRK continues to run smoothly with W2K3 SP2 and keep you posted.
If you have any contrary or supporting experiences in this regard, please feel free to let us know.
]]>PXR was designed as a middle- and back-end for the Microsoft C compiler by means of the Microsoft Phoenix compiler framework. That means, the compiler parses the source files and builds up its intermediate (internal) representation (IR) of the code. PXR then analyzes this IR to generate HTML output. In addition to the comiler information, we also leverage pre-processor output to analyze macros and their value at compile time.
One goal of PXR is to make access to certain aspects of the WRK sources as simple as possible, which is why PXR groups the resulting output into three categories: functions, types, and macros. We intentionally abstract from the file and module hierarchy, because when you start working with the WRK you start picking some functions and data structures first to continue digging into the internals. A fourth category, the typedef alias category, was added later for the sake of completeness. For each category, a list of items is shown in the navigation bar on the left side of the page shown above. The filter input allows a user to search for phrases in each of the categories. Besides of that, following is a list of features PXR provides.
Functions
For each function, PXR shows the following information section: the function signature, a description, a simplified call graph, callers, references, and the source code itself. Each section can be collapsed or expanded. In the following figure, only the signature, call graph, and source code section are open.
Data Type Dialogs
Whenever source code is shown on a PXR rendered page, it is both highlighted and hyperlinked. That means, if PXR is aware of further information for a particular identifier in the source code, it will embedd a link to the respective further information. For data types, we decided not to open another tab on the PXR screen, but rather to open a “window”, so as to not distract you from reading the source code of a function. However, if you want to follow a link to another function, PXR will open a new tab for this function as this item contains too much information to be reasonably presented in a window.
Source Code Folding
PXR allows you to fold away unnecessary code blocks, such as if and else blocks. This is to make reading and understanding the flow through the code more easily, as you can concentrate on particular code paths while making invisible those paths that are of minor interest. In the following figure, the if block on lines 4832 thru 4869 was folded away. It can be made visible again by clicking the “+” icon.
Interested in PXR?
We have made our version of the WRK sources available online to faculty members at www.dcl.hpi.uni-potsdam.de/wrk-pxr/. If you want to have a look yourself and you are a faculty member, please contact compsci@microsoft.com and we will be happy to create an account for you. We further plan to publish our HTML version of the source code at Faculty Connection at the beginning of next month. The PXR tool itself is currently not available but we are working on that. Please feel free to leave us any comments or requests for features.
]]>
Limits on Threads
The maximum number of threads in a system is limited by various resources. For example, on a 32-bit Windows system, the major limitation is the address space of a process. As each thread requires a certain number of memory in kernel space (NtCreateThread – memory allocations in kernel mode) and user space, you can easily calculate the number of threads you can create until the address space of the process is exhausted. In contrast, on 64-bit systems, the major limitation is not the address space, but the physical amount of memory plugged into the machine. For each thread, Windows allocates some memory in kernel space and also the thread stack in user mode. Once the total physical memory on a machine without paging is exhaustet, no further threads can be created.
Limitation by Physical Memory
The HPI recently established a laboratory – the Future SOC Lab – with absolutely stunning hardware. We managed to get access to a machine equipped with 64 cores and 1 TB of main memory to evaluate our hypothesis that once we would have enough memory in the machine, we could reach the theoretical limit.
So, how many threads could be created on such a machine? According to our calculation model a thread on a 64-bit Windows Server 2008 R2 system consumes at least 50KB of memory – using a minimal stack. Therefore we should be able to exceed the number of 20 million threads, spread across multiple processes.
Limitation by Kernel Constants
Having a closer look into the WRK, we found out another limit. The kernel itself holds a list of all process and thread IDs used in the system. And this list is an ordinary handle table. So the maximum size of 2^24 entries for a handle table also holds for the process/thread ID table.
The constant is defined in base\ntos\ex\handle.c, Line 101:
#define MAX_HANDLES (1<<24)Thus, the maximum number of threads in the whole system is limited to 16,777,216. So let’s try to consume all those thread IDs.
First Tests
Here is our test setup: we wrote a little C program, which creates as many threads as possible. Of course we created our threads only with minimal stack sizes and and let Windows only reserve but not commit stack allocations. Also, we started our threads only in a suspended execution mode.
Our program tries to create threads in parallel. Therefore we start some dedicated threads that create as many threads as possible in a loop. The routine for the dedicated threads looks like:
DWORD WINAPI parallelCreateThreadFunc(LPVOID data) { HANDLE hThread; // create threads until errors occur while(hThread = CreateThread(NULL, 0, sleepThread, NULL, CREATE_SUSPENDED, NULL)) { // close the handle to keep the process-wide handle table clean CloseHandle(hThread); // count the created threads in a thread-safe manner InterlockedIncrement(&totalThreadCount); } return 0; }
On another test system with 4GB of main memory, we reached the maximum of about 90,000 threads in 5 seconds. On the big system we stumbled upon an issue: Our test program created the first million of threads in about some minutes. Continuing to the next million, our program needed almost an hour! That was strange, so we decided to investigate the matter a little bit further. Unfortunately, we could not investigate it on the big Future SOC Lab machine, as our access time was restricted. Nevertheless, we will demonstrate that our findings apply to the big machine as well.
First tests indicated that the time for creating a thread increases with every new thread, instead of being constant. A linear correlation between the number of created threads and the creation time was documented by further measurements. But where is the bottleneck?
CSRSS
While starring at the taskmanager during our tests we noticed the increasing number of open handles in the CSRSS process. The more threads we created, the more handles showed up in Task Manager. During thread creation the number of threads and the open handles in CSRSS increased synchronously. But the reverse process, the termination of these threads, was not synchronized. The CSRSS took much more time for reducing the handle count towards zero. Maybe there’s the bottleneck. So what in the world is CSRSS doing?
The Windows Internals book told us that during process creation CSRSS is informed and a function named “CsrCreateProcess” is called via LPC. The Dependency Walker shows for csrss.exe that the corresponding csrsrv.dll also exports a function named “CsrCreateThread”. So maybe for thread creation something similar is going on.
Hash Table Full of Threads
In the WRK there are no sources for those DLLs. But the ReactOS project held some sources for CSRSS. Of course the ReactOS code is not the one underlying to our Windows Server 2008 R2, but the principles should be the same. The sources (thread.c) revealed that CSRSS maintains a hash table for threads inside of csrsrv.dll. It is implemented as chained hash table with doubly linked lists and only 256 buckets. Also, for every thread creation, CSRSS makes a lookup for the ID of current thread to receive the corresponding CSR_THREAD object.
Definition of the hash table in thread.c:
LIST_ENTRY CsrThreadHashTable[256];
Extract of the CsrCreateThread function in thread.c:
NTSTATUS NTAPI CsrCreateThread(IN PCSR_PROCESS CsrProcess, IN HANDLE hThread, IN PCLIENT_ID ClientId) { //... more code /* Get the current Process and make sure the Thread is valid with this CID */ CurrentThread = CsrLocateThreadByClientId(&CurrentProcess, &CurrentCid); //... more code }
Implementation of lookup function CsrLocateThreadByClientId in thread.c:
PCSR_THREAD NTAPI CsrLocateThreadByClientId(OUT PCSR_PROCESS *Process OPTIONAL, IN PCLIENT_ID ClientId) { ULONG i; PLIST_ENTRY ListHead, NextEntry; PCSR_THREAD FoundThread; /* Hash the Thread */ i = CsrHashThread(ClientId->UniqueThread); /* Set the list pointers */ ListHead = &CsrThreadHashTable[i]; NextEntry = ListHead->Flink; /* Star the loop */ while (NextEntry != ListHead) { /* Get the thread */ FoundThread = CONTAINING_RECORD(NextEntry, CSR_THREAD, HashLinks); /* Compare the CID */ if (FoundThread->ClientId.UniqueThread == ClientId->UniqueThread) { /* Match found, return the process */ *Process = FoundThread->Process; /* Return thread too */ return FoundThread; } /* Next */ NextEntry = NextEntry->Flink; } /* Nothing found */ return NULL; }
With millions of threads created that hash table would degenerate more or less into a simple list with linear search complexity. However, these findings are based on ReactOS, so we needed to verify that something similar is going on in Windows Server 2008 R2.
Drill Into Windows Server 2008 R2
We started WinDbg for inspecting our CSRSS. If you intend to follow our experiment, be careful not to break into CSRSS in the same session as your debugger runs, as otherwise the system will halt. Debugging another session works fine. When using the debugging symbols provided by Microsoft, the inspection was quite simple. We could verify that there is still a hash table called CSRSRV!CsrThreadHashTable with linked lists for its buckets. The size of the hash table changed however in Windows Server 2008 R2. We determined the actual size right from the underlying hash function. In ReactOS this function was a simple macro and in the disassembly the hashing algorithm was inlined in many places.
The formula is: (ThreadID / 4) % 1024. Thus, the hash table has 1024 buckets – but that’s still too small. To prove that this hash table has a significant impact on thread creation time, we patched the hash algorithm into a constant function. So every thread ID was placed into the first bucket of the hash table. The time for creating a thread increased by an order of magnitude, compared to our unpatched version. And indeed, on the small 4GB machine the creation process extremely slowed down.
So we could prove that the performance degradation correlates to CSRSS’s hash table, which means that we found the bottleneck. But how can we fix this?
Working Around the Scalability Issues
We had three alternatives: either increasing the number of hash table entries, replacing the hash table with another data structure, or not using it at all. As we lack the sources of CSRSS, we decided for the latter. In this regard, we were not fixing the issue but rather working around to see if our assumptions were correct.
We extended our test program so that it patches the WinAPI CreateThread function in memory in order to avoid any LPC calls to CsrCreateThread. Therefore, we replaced the call instruction in the kernel32 module (more precisely: kernelbase.dll) at the right offset with NOP instructions. But beware of changes in global shared memory without activating copy-on-write for the corresponding pages.
Surprisingly, this quick and dirty hack worked very well and everything went as before except that CSRSS wasn’t informed about newly created threads. Though, we must admit that we don’t know about all the implications of our modification. Maybe, if we would use “real working” threads instead of “sleeping” threads, something might fail.
However, thread creation was fast, really fast. Within seconds we created 4.7 million threads – maybe more than anyone did before. We will provide detailed results of our performance measurements soon.
In conclusion, we could verify that CSRSS is having scalability issues upon thread creation, specifically when there are millions of those. Also, we ran into yet another limitation, which we have not been able to resolve yet. We could only create 4.7 millions of threads but not 16 millions. As soon as we figured out that limitation, we will post it here.
]]>
In this article, we assume that you are using Sun’s VirtualBox, but it should work with other vendors as well.
First of all, you must install Windows Server 2003 with Service Pack 1 in your virtual machine. You may choose between the 32-bit or 64-bit version, but the service pack version must be the exact same! Once the installation is complete, we recommend adjusting the Windows Update policy such that updates are not installed automatically. This step is necessary, as Windows Update will probably download a higher service pack version, which the WRK might be incompatible with.
The WRK requires a multi-processor version of the hardware abstraction layer (HAL) library, which abstracts from all the chipset specific settings of your mainboard. If the VM is not a multi-processor VM, the Windows Server 2003 installation process may choose to install the single processor version of the HAL. To figure out, which HAL you have on your VM, go to the %systemroot%\system32 directory and right click on the HAL.dll file, and select Properties. In the appearing dialog, choose the Version tab and select the Internal Name property, which shows you the internal name of the selected file.
Depending on the internal name, you need to copy one of the following files from the Resources\Windows_Research_Kernel\Get_WRK\WRK-v1.2\WS03SP1HALS\ folder on the DVD to the %systemroot%\system32 on your VM.
| Internal Name | File to Copy |
| halacpi.dll | halacpim.dll |
| halaacpi.dll | halmacpi.dll |
| halapic.dll | halmps.dll |
Once you have built the WRK, copy the resulting executable (either wrkx86.exe or wrkamd64.exe) to the %systemroot%\system32 folder on the WRK VM.
The last step to make your VM WRK-ready is to update your boot.ini file, which resides in the root directory of our boot partition (probably C:\). To edit the file on your VM, click on the Start menu, right click My Computer and select Properties. In the properies dialog, click on the Advanced tab. In the Advanced tab, click on the Settings button in the Startup and Recovery section:
In the Startup and Recovery dialog, click the Edit button, which opens the boot.ini file in the Notepad editor.
Edit the file as follows:
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(2)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Windows Server 2003, Standard"
multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Windows Server 2003, WRK" /kernel=wrkx86.exe /hal=<your HAL> /debug /debugport=com1
multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Windows Server 2003, WRK - No Debugger" /kernel=wrkx86.exe /hal=<your HAL>
Update <your HAL> with the appropriate file name from the above table. Note that the filenames must be short (8.3) names.
Information about aditional boot options, such as the /debug switch, can be found here. For configuring the debugger, you may also have a look at this post. Do not forget to safe the file. You can then close the editor.
Once you configured the boot.ini file, re-boot the system. If you configured the operating system name of your boot.ini file as mentioned above, you should now see the following boot screen:
The second entry boots up your WRK with the debugger stub being active, while the last entry just boots your kernel without any debug features.
]]>As the WRK and CRK community is pretty active in the Asia/Pacific region, we are extremely proud to be invited to give a presentation about our experiences with the WRK here at HPI. Here is the abstract of Alexander’s talk.
The Windows Research Kernel (WRK) was released in 2006. Since then, at the Operating Systems and Middleware group at HPI, we have been using the WRK as a valuable complement to the Windows Internals Curriculum Resource Kit (CRK). However, although the WRK was especially designed for academia, it is still a software product that has to be carefully adapted to fit into the curriculum. The biggest challenges in this regard were the sheer complexity of the kernel, the high configuration overhead prior to usage, and the lack of suitable experiments, which we consider essential for teaching operating systems.
In this talk, we present our hands-on methodology for teaching operating systems and the lessons we learned from using the WRK in the classroom. We, therefore, designed and implemented a couple of projects that align nicely with the CRK and that help students easily master kernel programming projects inside the WRK. The talk concludes with interesting results of a survey among our students and some thoughts on and suggestions of future directions for the WRK we would be delighted to see.
The slides as well as a video of Alex’ presentation are finally available