Explaining Assembly Language Technology: Retrieving and Formatting Readable Time in Kernel Drivers

Core API for Time Retrieval and Conversion

Key Data Structures

; TIME_FIELDS structure definition
TIME_FIELDS STRUCT
    Year        WORD ?      ; Year (1601-30827)
    Month       WORD ?      ; Month (1-12)
    Day         WORD ?      ; Day (1-31)
    Hour        WORD ?      ; Hour (0-23)
    Minute      WORD ?      ; Minute (0-59)
    Second      WORD ?      ; Second (0-59)
    Milliseconds WORD ?     ; Milliseconds (0-999)
    Weekday     WORD ?      ; Weekday (0-6, 0=Sunday)
TIME_FIELDS ENDS

Complete Implementation of Time Retrieval and Formatting

Function Implementation Code

; Get formatted time string
; Output: eax - points to static buffer (format: "YYYY.MM.DD.HH.MM.SS")
GetFormattedTimeString proc
    local systemTime:LARGE_INTEGER
    local localTime:LARGE_INTEGER
    local timeFields:TIME_FIELDS

    ; 1. Get current system time (UTC)
    invoke KeQuerySystemTime, addr systemTime

    ; 2. Convert to local time
    invoke ExSystemTimeToLocalTime, addr systemTime, addr localTime

    ; 3. Convert to readable time fields
    invoke RtlTimeToTimeFields, addr localTime, addr timeFields

    ; 4. Format string
    ; Use static buffer (consider thread safety in actual driver)
    mov eax, offset szTimeBuffer

    ; Format year (4 digits)
    movzx ecx, timeFields.Year
    invoke RtlIntegerToUnicodeString, ecx, 10, eax, offset szTimeBuffer

    ; Add separator
    mov byte ptr [eax+4], '.'

    ; Format month (2 digits)
    movzx ecx, timeFields.Month
    invoke FormatTwoDigits, ecx, eax+5

    ; Add separator
    mov byte ptr [eax+7], '.'

    ; Format day (2 digits)
    movzx ecx, timeFields.Day
    invoke FormatTwoDigits, ecx, eax+8

    ; Add separator
    mov byte ptr [eax+10], '.'

    ; Format hour (2 digits)
    movzx ecx, timeFields.Hour
    invoke FormatTwoDigits, ecx, eax+11

    ; Add separator
    mov byte ptr [eax+13], '.'

    ; Format minute (2 digits)
    movzx ecx, timeFields.Minute
    invoke FormatTwoDigits, ecx, eax+14

    ; Add separator
    mov byte ptr [eax+16], '.'

    ; Format second (2 digits)
    movzx ecx, timeFields.Second
    invoke FormatTwoDigits, ecx, eax+17

    ; String terminator
    mov byte ptr [eax+19], 0

    mov eax, offset szTimeBuffer
    ret
GetFormattedTimeString endp

; Helper function: Format two-digit numbers
; Input: ecx - number, edi - output buffer
FormatTwoDigits proc
    push edx
    push ebx

    mov eax, ecx
    xor edx, edx
    mov ebx, 10
    div ebx             ; eax = tens, edx = units

    add al, '0'         ; Convert to ASCII
    mov [edi], al

    add dl, '0'
    mov [edi+1], dl

    pop ebx
    pop edx
    ret
FormatTwoDigits endp

; Data segment definition
.data
szTimeBuffer db 20 dup(0)   ; Sufficient to hold "YYYY.MM.DD.HH.MM.SS"

Step-by-Step Code Analysis

  1. System Time Retrieval:

    invoke KeQuerySystemTime, addr systemTime
    
  • Retrieves UTC time, the number of 100-nanosecond intervals since January 1, 1601
  • Local Time Conversion:

    invoke ExSystemTimeToLocalTime, addr systemTime, addr localTime
    
    • Converts to local time considering timezone settings
  • Time Field Decomposition:

    invoke RtlTimeToTimeFields, addr localTime, addr timeFields
    
    • Decomposes the 64-bit time value into readable date-time components
  • String Formatting:

    • Uses <span>RtlIntegerToUnicodeString</span> to convert the year
    • Custom <span>FormatTwoDigits</span> handles month, day, hour, minute, second
    • Adds separators to build the final string

    Practical Application Examples

    Driver Logging

    ; Log debug information with timestamp
    LogWithTimestamp proc pMessage:DWORD
        local szTime[20]:BYTE
    
        ; Get formatted time
        call GetFormattedTimeString
    
        ; Output log
        invoke DbgPrint, offset szLogFormat, eax, pMessage
    
        ret
    LogWithTimestamp endp
    
    .data
    szLogFormat db "[%s] %s", 0Ah, 0
    

    File Timestamp

    ; Create filename with timestamp
    GenerateTimestampFilename proc
        local szTime[20]:BYTE
    
        ; Get current time
        call GetFormattedTimeString
    
        ; Build filename
        invoke RtlStringCbPrintf, offset szFilename, 
                                 sizeof szFilename,
                                 offset szFileFormat,
                                 eax
    
        ret
    GenerateTimestampFilename endp
    
    .data
    szFileFormat db "Log_%s.txt", 0
    

    Key Considerations

    1. Thread Safety:

    • The above implementation uses a static buffer, which is not suitable for multi-threaded environments
    • In actual drivers, thread-local storage or passing output buffers should be used
  • Buffer Management:

    ; Safe version interface
    GetFormattedTimeStringEx proc pBuffer:DWORD, bufferSize:DWORD
        ; Add buffer length check
        cmp bufferSize, 20
        jb .buffer_too_small
        ; ...remaining implementation...
    .buffer_too_small:
        mov eax, STATUS_BUFFER_TOO_SMALL
        ret
    
  • Performance Considerations:

    • Frequent calls to time functions may impact performance
    • For high-frequency logging, consider caching time values
  • Timezone Handling:

    • <span>ExSystemTimeToLocalTime</span> uses the system’s current timezone settings
    • The driver cannot control user-modified timezones

    Extended Functionality Implementation

    Adding Millisecond Display

    ; Add milliseconds to the time string
        ; ...existing code...
    
        ; Add milliseconds after seconds
        mov byte ptr [eax+19], '.'
        movzx ecx, timeFields.Milliseconds
        invoke FormatThreeDigits, ecx, eax+20
    
        ; Update string terminator
        mov byte ptr [eax+23], 0
    
        ; Buffer needs to be expanded to 24 bytes
    

    Multi-language Weekday Display

    ; Add weekday display
    AppendWeekday proc pBuffer:DWORD, weekday:WORD
        movzx eax, weekday
        mov ecx, offset WeekdayNames
        mov edx, [ecx + eax*4]
        invoke RtlStringCbCat, pBuffer, MAX_PATH, edx
        ret
    AppendWeekday endp
    
    .data
    WeekdayNames dd offset szSunday, offset szMonday, offset szTuesday
                 dd offset szWednesday, offset szThursday, offset szFriday
                 dd offset szSaturday
    szSunday    db "Sun",0
    szMonday    db "Mon",0
    ; ...other weekday names...
    

    Through this time handling mechanism, kernel drivers can generate human-readable time information for logging, file naming, and various other scenarios. Understanding the processes of retrieving, converting, and formatting time values is crucial for developing reliable system-level software.

    Leave a Comment