Assembly Language Techniques: Accurate Time Acquisition and Timing Technologies

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:

  1. Calendar Time: Used for logging and file timestamps
  2. Boot Time: Used for performance analysis and random number seeds
  3. 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

  1. 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

    1. 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:
      
    2. 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

    1. Driver Log Timestamps:

      LogMessage proc pMessage:DWORD
          local tickCount:DWORD
      
          invoke MyGetTickCount, addr tickCount
          invoke DbgPrint, offset szLogFormat, tickCount, pMessage
          ret
      LogMessage endp
      
    2. Performance Bottleneck Analysis:

      ; Critical path performance measurement
      invoke MeasureExecutionTime
      
    3. 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.

    Leave a Comment