Principles and Applications of System Time Acquisition
Overview of Windows Time System
The Windows system provides various methods for time acquisition, suitable for different scenarios:
- Calendar Time: Used for logging and file timestamps
- Boot Time: Used for performance analysis and random number seeds
- High-Precision Timing: Used for microsecond-level precise measurements
Kernel Mode Time Acquisition Techniques
KeQueryTickCount and KeQueryTimeIncrement
; Get milliseconds since system boot
; Input: pMsec - pointer to DWORD buffer to receive milliseconds
MyGetTickCount proc pMsec:DWORD
local tickCount:LARGE_INTEGER
local timeIncrement:ULONG
local result:QWORD
; Get time increment (100-nanosecond units per tick)
invoke KeQueryTimeIncrement
mov timeIncrement, eax
; Get current tick count
invoke KeQueryTickCount, addr tickCount
; Calculate milliseconds: (tickCount * timeIncrement) / 10000
mov eax, tickCount.LowPart
mul timeIncrement ; EDX:EAX = tickCount * timeIncrement
shl edx, 32 ; Convert result to 64-bit EDX:EAX
add edx, eax
mov ecx, 10000 ; Convert to milliseconds (100-nanoseconds to milliseconds)
div ecx ; EAX = milliseconds
; Store result
mov ecx, pMsec
mov [ecx], eax
ret
MyGetTickCount endp
Code Analysis
-
KeQueryTimeIncrement:
- Returns the number of 100-nanosecond units per clock tick
- Modern systems typically have 156250 (15.625ms) or 390625 (39.0625ms)
KeQueryTickCount:
- Returns the number of clock ticks since system boot
- 64-bit counter to avoid the 32-bit counter wraparound issue (~49.7 days)
Unit Conversion:
- Formula: milliseconds = (tickCount × timeIncrement) ÷ 10,000
- Since 1 millisecond = 10,000 100-nanosecond units
User Mode Corresponding Function Comparison
Implementation Principle of GetTickCount
; Approximate implementation of user mode GetTickCount
MyGetTickCount proc
local tickCount:LARGE_INTEGER
local timeIncrement:DWORD
; Kernel call (actual GetTickCount is implemented via system call)
invoke NtQuerySystemInformation, SystemTimeInformation,
addr tickCount,
sizeof LARGE_INTEGER,
NULL
; Assume timeIncrement is known to be 156250 (15.625ms)
mov timeIncrement, 156250
; Calculate milliseconds
mov eax, tickCount.LowPart
mul timeIncrement
mov ecx, 10000
div ecx
ret
MyGetTickCount endp
High-Precision Timing Techniques
Performance Counter Implementation
; Get high-precision timing (microsecond level)
GetHighResolutionTime proc pMicrosec:DWORD
local perfCount:LARGE_INTEGER
local perfFreq:LARGE_INTEGER
; Get performance counter frequency
invoke KeQueryPerformanceCounter, addr perfFreq
; Get current counter value
invoke KeQueryPerformanceCounter, addr perfCount
; Calculate microseconds: (perfCount * 1,000,000) / perfFreq
mov eax, perfCount.LowPart
mov edx, perfCount.HighPart
mov ecx, 1000000
div perfFreq.LowPart
; Store result
mov ecx, pMicrosec
mov [ecx], eax
ret
GetHighResolutionTime endp
Time Measurement Application Examples
Performance Analysis Code Block
; Measure code execution time (kernel mode)
MeasureExecutionTime proc
local startTime:LARGE_INTEGER
local endTime:LARGE_INTEGER
local elapsed:QWORD
local timeIncrement:ULONG
; Get time increment
invoke KeQueryTimeIncrement
mov timeIncrement, eax
; Record start time
invoke KeQueryTickCount, addr startTime
; Code block to be measured
; ...
; Record end time
invoke KeQueryTickCount, addr endTime
; Calculate elapsed time (milliseconds)
mov eax, endTime.LowPart
sub eax, startTime.LowPart
mul timeIncrement
mov ecx, 10000
div ecx
; EAX now contains milliseconds
invoke DbgPrint, offset szTimeFormat, eax
ret
MeasureExecutionTime endp
Random Number Seed Generation
Time-Based Random Seed
; Generate a time-based random seed
GenerateTimeBasedSeed proc
local tickCount:DWORD
; Get system tick count
invoke MyGetTickCount, addr tickCount
; Combine with current processor counter and tick value
invoke KeGetCurrentProcessorNumber
xor eax, tickCount
; Further obfuscation (optional)
rdtsc ; Read timestamp counter
xor eax, edx
xor eax, tickCount
ret ; EAX contains random seed
GenerateTimeBasedSeed endp
Key Considerations
-
Counter Wraparound Issue:
; Safe time difference calculation mov eax, endTime.LowPart sub eax, startTime.LowPart jnc .no_wrap add eax, 0FFFFFFFFh ; Handle wraparound .no_wrap: -
Multi-Processor Synchronization:
- Different CPU cores may have different TSC (timestamp counter) values
- Use
<span>KeQueryInterruptTime</span>to get synchronized time
Precision vs. Performance Trade-off:
<span>KeQueryTickCount</span>: Low overhead, low precision (~15ms)<span>KeQueryPerformanceCounter</span>: High overhead, high precision (~μs)
Practical Application Scenarios
-
Driver Log Timestamps:
LogMessage proc pMessage:DWORD local tickCount:DWORD invoke MyGetTickCount, addr tickCount invoke DbgPrint, offset szLogFormat, tickCount, pMessage ret LogMessage endp -
Performance Bottleneck Analysis:
; Critical path performance measurement invoke MeasureExecutionTime -
Timeout Control:
; Wait with timeout WaitWithTimeout proc pObject:DWORD, timeoutMs:DWORD local startTick:DWORD local currentTick:DWORD invoke MyGetTickCount, addr startTick .while TRUE ; Check object state ; ... ; Check timeout invoke MyGetTickCount, addr currentTick mov eax, currentTick sub eax, startTick cmp eax, timeoutMs jae .timeout ; Short delay invoke KeStallExecutionProcessor, 1000 ; 1ms delay .endw .timeout: mov eax, STATUS_TIMEOUT ret WaitWithTimeout endp
By mastering these time acquisition and timing techniques, developers can implement precise time measurement, logging, and performance analysis functions in kernel drivers. Understanding the characteristics and limitations of different time APIs is crucial for writing reliable high-performance drivers.