www.pudn.com > win2ksrc.rar > elfexts.c, change:2000-07-26,size:21983b


/*++ 
 
Copyright (c) 1990  Microsoft Corporation 
 
Module Name: 
 
    elfexts.c 
 
Abstract: 
 
    This function contains the eventlog ntsd debugger extensions 
 
Author: 
 
    Dan Hinsley (DanHi) 22-May-1993 
 
Revision History: 
 
--*/ 
 
#include <nt.h> 
#include <ntrtl.h> 
#include <nturtl.h> 
#include <windows.h> 
#include <ntsdexts.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <malloc.h> 
#include <time.h> 
#include <elf.h> 
#include <elfdef.h> 
#include <elfcommn.h> 
#include <elfproto.h> 
#include <svcs.h> 
#include <elfextrn.h> 
 
//#define DbgPrint(_x_) (lpOutputRoutine) _x_ 
#define DbgPrint(_x_) 
#define MAX_NAME 256 
#define printf (lpOutputRoutine) 
#define GET_DATA(DebugeeAddr, LocalAddr, Length) \ 
    Status = ReadProcessMemory(                  \ 
                GlobalhCurrentProcess,           \ 
                (LPVOID)DebugeeAddr,             \ 
                LocalAddr,                       \ 
                Length,                          \ 
                NULL                             \ 
                ); 
 
PNTSD_OUTPUT_ROUTINE lpOutputRoutine; 
PNTSD_GET_EXPRESSION lpGetExpressionRoutine; 
PNTSD_CHECK_CONTROL_C lpCheckControlCRoutine; 
 
HANDLE GlobalhCurrentProcess; 
BOOL Status; 
 
// 
// Initialize the global function pointers 
// 
 
VOID 
InitFunctionPointers( 
    HANDLE hCurrentProcess, 
    PNTSD_EXTENSION_APIS lpExtensionApis 
    ) 
{ 
    // 
    // Load these to speed access if we haven't already 
    // 
 
    if (!lpOutputRoutine) { 
        lpOutputRoutine = lpExtensionApis->lpOutputRoutine; 
        lpGetExpressionRoutine = lpExtensionApis->lpGetExpressionRoutine; 
        lpCheckControlCRoutine = lpExtensionApis->lpCheckControlCRoutine; 
    } 
 
    // 
    // Stick this in a global 
    // 
 
    GlobalhCurrentProcess = hCurrentProcess; 
} 
 
LPWSTR 
GetUnicodeString( 
    PUNICODE_STRING pUnicodeString 
    ) 
{ 
    DWORD Pointer; 
    UNICODE_STRING UnicodeString; 
 
    GET_DATA(pUnicodeString, &UnicodeString, sizeof(UNICODE_STRING)) 
    Pointer = (DWORD) UnicodeString.Buffer; 
    UnicodeString.Buffer = (LPWSTR) LocalAlloc(LMEM_ZEROINIT, 
        UnicodeString.Length + sizeof(WCHAR)); 
    GET_DATA(Pointer, UnicodeString.Buffer, UnicodeString.Length) 
 
    return(UnicodeString.Buffer); 
} 
 
DWORD 
GetLogFileAddress( 
    LPSTR LogFileName, 
    PLOGFILE LogFile 
    ) 
{ 
    ANSI_STRING AnsiString; 
    UNICODE_STRING UnicodeString; 
    DWORD Pointer; 
    DWORD LogFileAnchor; 
    LPWSTR ModuleName; 
 
    // 
    // Convert the string to UNICODE 
    // 
 
    RtlInitAnsiString(&AnsiString, LogFileName); 
    RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE); 
 
    // 
    // Walk the logfile list looking for a match 
    // 
 
    LogFileAnchor = (lpGetExpressionRoutine)("LogFilesHead"); 
 
    GET_DATA(LogFileAnchor, &Pointer, sizeof(DWORD)) 
 
    while (Pointer != LogFileAnchor) { 
        GET_DATA(Pointer, LogFile, sizeof(LOGFILE)) 
        ModuleName = GetUnicodeString(LogFile->LogModuleName); 
        if (!wcsicmp(ModuleName, UnicodeString.Buffer)) { 
            break; 
        } 
        LocalFree(ModuleName); 
        Pointer = (DWORD) LogFile->FileList.Flink; 
    } 
 
    RtlFreeUnicodeString(&UnicodeString); 
 
    if (Pointer == LogFileAnchor) { 
        return(0); 
    } 
    else { 
        LocalFree(ModuleName); 
        return(Pointer); 
    } 
} 
 
// 
// Dump an individual record 
// 
 
DWORD 
DumpRecord( 
    DWORD Record, 
    DWORD RecordNumber, 
    DWORD StartOfFile, 
    DWORD EndOfFile 
    ) 
{ 
    DWORD BufferLen; 
    PCHAR TimeBuffer; 
    PEVENTLOGRECORD EventLogRecord; 
    LPWSTR Module; 
    LPWSTR Computer; 
    DWORD FirstPiece = 0; 
 
    GET_DATA(Record, &BufferLen, sizeof(DWORD)) 
 
    // 
    // See if it's a ELF_SKIP_DWORD, and if it is, return the top of the 
    // file 
    // 
 
    if (BufferLen == ELF_SKIP_DWORD) { 
        return(StartOfFile + sizeof(ELF_LOGFILE_HEADER)); 
    } 
 
    // 
    // See if it's the EOF record 
    // 
 
    if (BufferLen == ELFEOFRECORDSIZE) { 
        return(0); 
    } 
 
    BufferLen += sizeof(DWORD); // get room for length of next record 
    EventLogRecord = (PEVENTLOGRECORD) LocalAlloc(LMEM_ZEROINIT, BufferLen); 
 
    // 
    // If the record wraps, grab it piecemeal 
    // 
 
    if (EndOfFile && BufferLen + Record > EndOfFile) { 
        FirstPiece = EndOfFile - Record; 
        GET_DATA(Record, EventLogRecord, FirstPiece); 
        GET_DATA((StartOfFile + sizeof(ELF_LOGFILE_HEADER)), 
            ((PBYTE) EventLogRecord + FirstPiece), BufferLen - FirstPiece) 
    } 
    else { 
        GET_DATA(Record, EventLogRecord, BufferLen) 
    } 
 
    // 
    // If it's greater than the starting record, print it out 
    // 
 
    if (EventLogRecord->RecordNumber >= RecordNumber) { 
        printf("\nRecord %d is %d [0x%X] bytes long starting at 0x%X\n", 
            EventLogRecord->RecordNumber, EventLogRecord->Length, 
            EventLogRecord->Length, Record); 
        Module = (LPWSTR)(EventLogRecord+1); 
        Computer = (LPWSTR)((PBYTE) Module + ((wcslen(Module) + 1) * sizeof(WCHAR))); 
        printf("\tGenerated by %ws from system %ws\n", Module, Computer); 
 
        TimeBuffer = ctime((time_t *)&(EventLogRecord->TimeGenerated)); 
        if (TimeBuffer) { 
            printf("\tGenerated at %s", TimeBuffer); 
        } 
        else { 
            printf("\tGenerated time field is blank\n"); 
        } 
        TimeBuffer = ctime((time_t *)&(EventLogRecord->TimeWritten)); 
        if (TimeBuffer) { 
            printf("\tWritten at %s", TimeBuffer); 
        } 
        else { 
            printf("\tTime written field is blank\n"); 
        } 
 
        printf("\tEvent Id = %d\n", EventLogRecord->EventID); 
        printf("\tEventType = "); 
        switch (EventLogRecord->EventType) { 
            case EVENTLOG_SUCCESS: 
                printf("Success\n"); 
                break; 
            case EVENTLOG_ERROR_TYPE: 
                printf("Error\n"); 
                break; 
            case EVENTLOG_WARNING_TYPE: 
                printf("Warning\n"); 
                break; 
            case EVENTLOG_INFORMATION_TYPE: 
                printf("Information\n"); 
                break; 
            case EVENTLOG_AUDIT_SUCCESS: 
                printf("Audit Success\n"); 
                break; 
            case EVENTLOG_AUDIT_FAILURE: 
                printf("Audit Failure\n"); 
                break; 
            default: 
                printf("Invalid value 0x%X\n", EventLogRecord->EventType); 
        } 
        printf("\t%d strings at offset 0x%X\n", EventLogRecord->NumStrings, 
            EventLogRecord->StringOffset); 
        printf("\t%d bytes of data at offset 0x%X\n", EventLogRecord->DataLength, 
            EventLogRecord->DataOffset); 
    } 
 
    if (FirstPiece) { 
        Record = StartOfFile + sizeof(ELF_LOGFILE_HEADER) + BufferLen - 
            FirstPiece; 
    } 
    else { 
        Record += EventLogRecord->Length; 
    } 
 
    LocalFree(EventLogRecord); 
    return(Record); 
} 
 
// 
// Dump a record, or all records, or n records 
// 
 
VOID 
record( 
    HANDLE hCurrentProcess, 
    HANDLE hCurrentThread, 
    DWORD dwCurrentPc, 
    PNTSD_EXTENSION_APIS lpExtensionApis, 
    LPSTR lpArgumentString 
    ) 
{ 
    DWORD Pointer; 
    LOGFILE LogFile; 
    DWORD StartOfFile; 
    DWORD EndOfFile = 0; 
    DWORD RecordNumber = 0; 
 
    InitFunctionPointers(hCurrentProcess, lpExtensionApis); 
 
    // 
    // Evaluate the argument string to get the address of 
    // the record to dump. 
    // 
 
    if (lpArgumentString && *lpArgumentString) { 
        if (*lpArgumentString == '.') { 
            if (GetLogFileAddress(lpArgumentString+1, &LogFile) == 0) { 
                printf("Logfile %s not found\n", lpArgumentString+1); 
                return; 
            } 
            Pointer = ((DWORD) (LogFile.BaseAddress)) + LogFile.BeginRecord; 
        } 
        else if (*lpArgumentString == '#') { 
            RecordNumber = atoi(lpArgumentString + 1); 
            printf("Dumping records starting at record #%d\n", RecordNumber); 
            lpArgumentString = NULL; 
        } 
        else if (*lpArgumentString) { 
            Pointer = (lpGetExpressionRoutine)(lpArgumentString); 
        } 
        else { 
            printf("Invalid lead character 0x%02X\n", *lpArgumentString); 
            return; 
        } 
    } 
 
    if (!lpArgumentString || *lpArgumentString) { 
        if (GetLogFileAddress("system", &LogFile) == 0) { 
            printf("System Logfile not found\n"); 
            return; 
        } 
        Pointer = ((DWORD) (LogFile.BaseAddress)) + LogFile.BeginRecord; 
    } 
 
    StartOfFile = (DWORD) LogFile.BaseAddress; 
    EndOfFile = (DWORD) LogFile.BaseAddress + LogFile.ActualMaxFileSize; 
 
    // 
    // Dump records starting wherever they told us to 
    // 
 
    while (Pointer < EndOfFile && Pointer && !(lpCheckControlCRoutine)()) { 
        Pointer = DumpRecord(Pointer, RecordNumber, StartOfFile, EndOfFile); 
    } 
 
 
    return; 
} 
 
// 
// Dump a single LogModule structure if it matches MatchName (NULL matches 
// all) 
// 
 
PLIST_ENTRY 
DumpLogModule( 
    HANDLE hCurrentProcess, 
    DWORD pLogModule, 
    LPWSTR MatchName 
    ) 
{ 
    LOGMODULE LogModule; 
    WCHAR ModuleName[MAX_NAME / sizeof(WCHAR)]; 
 
    GET_DATA(pLogModule, &LogModule, sizeof(LogModule)) 
    GET_DATA(LogModule.ModuleName, &ModuleName, MAX_NAME) 
 
    if (!MatchName || !wcsicmp(MatchName, ModuleName)) { 
        printf("\tModule Name %ws\n", ModuleName); 
        printf("\tModule Atom 0x%X\n", LogModule.ModuleAtom); 
        printf("\tPointer to LogFile 0x%X\n", LogModule.LogFile); 
    } 
 
    return (LogModule.ModuleList.Flink); 
} 
 
// 
// Dump selected, or all, LogModule structures 
// 
 
VOID 
logmodule( 
    HANDLE hCurrentProcess, 
    HANDLE hCurrentThread, 
    DWORD dwCurrentPc, 
    PNTSD_EXTENSION_APIS lpExtensionApis, 
    LPSTR lpArgumentString 
    ) 
{ 
    DWORD pLogModule; 
    DWORD LogModuleAnchor; 
    LPWSTR wArgumentString = NULL; 
    ANSI_STRING AnsiString; 
    UNICODE_STRING UnicodeString; 
 
    InitFunctionPointers(hCurrentProcess, lpExtensionApis); 
    UnicodeString.Buffer = NULL; 
 
    // 
    // Evaluate the argument string to get the address of 
    // the logmodule to dump.  If no parm, dump them all. 
    // 
 
    if (lpArgumentString && *lpArgumentString == '.') { 
        lpArgumentString++; 
        RtlInitAnsiString(&AnsiString, lpArgumentString); 
        RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE); 
    } 
    else if (lpArgumentString && *lpArgumentString) { 
        pLogModule = (lpGetExpressionRoutine)(lpArgumentString); 
        DumpLogModule(hCurrentProcess, pLogModule, NULL); 
        return; 
    } 
 
    LogModuleAnchor = (lpGetExpressionRoutine)("LogModuleHead"); 
 
    GET_DATA(LogModuleAnchor, &pLogModule, sizeof(DWORD)) 
 
    while (pLogModule != LogModuleAnchor && !(lpCheckControlCRoutine)()) { 
        pLogModule = 
            (DWORD) DumpLogModule(hCurrentProcess, pLogModule, 
                UnicodeString.Buffer); 
        if (!UnicodeString.Buffer) { 
            printf("\n"); 
        } 
    } 
    if (UnicodeString.Buffer) { 
        RtlFreeUnicodeString(&UnicodeString); 
    } 
 
    return; 
} 
 
// 
// Dump a single LogFile structure if it matches MatchName (NULL matches 
// all) 
// 
 
PLIST_ENTRY 
DumpLogFile( 
    HANDLE hCurrentProcess, 
    DWORD pLogFile, 
    LPWSTR MatchName 
    ) 
{ 
    LOGFILE LogFile; 
    LPWSTR UnicodeName; 
 
    // 
    // Get the fixed part of the structure 
    // 
 
    GET_DATA(pLogFile, &LogFile, sizeof(LogFile)) 
 
    // 
    // Get the Default module name 
    // 
 
    UnicodeName = GetUnicodeString(LogFile.LogModuleName); 
 
    // 
    // See if we're just looking for a particular one.  If we are and 
    // this isn't it, bail out. 
    // 
 
    if (MatchName && wcsicmp(MatchName, UnicodeName)) { 
        LocalFree(UnicodeName); 
        return (LogFile.FileList.Flink); 
    } 
 
    // 
    // Otherwise print it out 
    // 
 
    printf("%ws", UnicodeName); 
    LocalFree(UnicodeName); 
 
    // 
    // Now the file name of this logfile 
    // 
 
    UnicodeName = GetUnicodeString(LogFile.LogFileName); 
    printf(" : %ws\n", UnicodeName); 
    LocalFree(UnicodeName); 
 
    if (LogFile.Notifiees.Flink == LogFile.Notifiees.Blink) { 
        printf("\tNo active ChangeNotifies on this log\n"); 
    } 
    else { 
        printf("\tActive Change Notify!  Dump of this list not implemented\n"); 
    } 
 
    printf("\tReference Count: %d\n\tFlags: ", LogFile.RefCount); 
    if (LogFile.Flags == 0) { 
        printf("No flags set "); 
    } 
    else { 
        if (LogFile.Flags & ELF_LOGFILE_HEADER_DIRTY) { 
            printf("Dirty "); 
        } 
        if (LogFile.Flags & ELF_LOGFILE_HEADER_WRAP) { 
            printf("Wrapped "); 
        } 
        if (LogFile.Flags & ELF_LOGFILE_LOGFULL_WRITTEN) { 
             printf("Logfull Written "); 
        } 
    } 
    printf("\n"); 
 
    printf("\tMax Files Sizes [Cfg:Curr:Next]  0x%X : 0x%X : 0x%X\n", 
        LogFile.ConfigMaxFileSize, LogFile.ActualMaxFileSize, 
        LogFile.NextClearMaxFileSize); 
 
    printf("\tRecord Numbers [Oldest:Curr] %d : %d\n", 
        LogFile.OldestRecordNumber, LogFile.CurrentRecordNumber); 
 
    printf("\tRetention period in days: %d\n", LogFile.Retention / 86400); 
 
    printf("\tBase Address: 0x%X\n", LogFile.BaseAddress); 
 
    printf("\tView size: 0x%X\n", LogFile.ViewSize); 
 
    printf("\tOffset of beginning record: 0x%X\n", LogFile.BeginRecord); 
 
    printf("\tOffset of ending record: 0x%X\n", LogFile.EndRecord); 
 
    return (LogFile.FileList.Flink); 
} 
 
// 
// Dump selected, or all, LogFile structures 
// 
 
VOID 
logfile( 
    HANDLE hCurrentProcess, 
    HANDLE hCurrentThread, 
    DWORD dwCurrentPc, 
    PNTSD_EXTENSION_APIS lpExtensionApis, 
    LPSTR lpArgumentString 
    ) 
{ 
    DWORD pLogFile; 
    DWORD LogFileAnchor; 
    LPWSTR wArgumentString = NULL; 
    ANSI_STRING AnsiString; 
    UNICODE_STRING UnicodeString; 
    BOOL AllocateString = FALSE; 
 
    InitFunctionPointers(hCurrentProcess, lpExtensionApis); 
    UnicodeString.Buffer = NULL; 
 
    // 
    // Evaluate the argument string to get the address of 
    // the logfile to dump.  If no parm, dump them all. 
    // 
 
    if (lpArgumentString && *lpArgumentString) { 
        if(*lpArgumentString == '.') { 
            lpArgumentString++; 
            RtlInitAnsiString(&AnsiString, lpArgumentString); 
            RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE); 
        } 
        else { 
            pLogFile = (lpGetExpressionRoutine)(lpArgumentString); 
            DumpLogFile(hCurrentProcess, pLogFile, NULL); 
            return; 
        } 
    } 
 
    LogFileAnchor = (lpGetExpressionRoutine)("LogFilesHead"); 
 
    GET_DATA(LogFileAnchor, &pLogFile, sizeof(DWORD)) 
 
    while (pLogFile != LogFileAnchor) { 
        pLogFile = 
            (DWORD) DumpLogFile(hCurrentProcess, pLogFile, 
                UnicodeString.Buffer); 
        if (!UnicodeString.Buffer) { 
            printf("\n"); 
        } 
    } 
 
    if (UnicodeString.Buffer) { 
        RtlFreeUnicodeString(&UnicodeString); 
    } 
 
    return; 
} 
 
// 
// Dump a request packet structure 
// 
 
VOID 
request( 
    HANDLE hCurrentProcess, 
    HANDLE hCurrentThread, 
    DWORD dwCurrentPc, 
    PNTSD_EXTENSION_APIS lpExtensionApis, 
    LPSTR lpArgumentString 
    ) 
{ 
    ELF_REQUEST_RECORD Request; 
    DWORD Pointer; 
    DWORD RecordSize; 
    WRITE_PKT WritePkt; 
    READ_PKT ReadPkt; 
    CLEAR_PKT ClearPkt; 
    BACKUP_PKT BackupPkt; 
    LPWSTR FileName; 
    CHAR Address[18]; 
 
    InitFunctionPointers(hCurrentProcess, lpExtensionApis); 
 
    // 
    // Evaluate the argument string to get the address of 
    // the request packet to dump. 
    // 
 
    if (lpArgumentString && *lpArgumentString) { 
        Pointer = (lpGetExpressionRoutine)(lpArgumentString); 
    } 
    else { 
        printf("Must supply a request packet address\n"); 
        return; 
    } 
 
    GET_DATA(Pointer, &Request, sizeof(ELF_REQUEST_RECORD)) 
 
    switch (Request.Command ) { 
        case ELF_COMMAND_READ: 
            printf("\nRead packet\n"); 
            GET_DATA(Request.Pkt.ReadPkt, &ReadPkt, sizeof(READ_PKT)) 
            printf("\tLast Seek Position = %d\n", ReadPkt.LastSeekPos); 
            printf("\tLast Seek Record = %d\n", ReadPkt.LastSeekRecord); 
            printf("\tStart at record number %d\n", ReadPkt.RecordNumber); 
            printf("\tRead %d bytes into buffer at 0x%X\n", 
                ReadPkt.BufferSize, ReadPkt.Buffer); 
            if (ReadPkt.Flags & ELF_IREAD_UNICODE) { 
                printf("\tReturn in ANSI\n"); 
            } 
            else { 
                printf("\tReturn in UNICODE\n"); 
            } 
            printf("\tRead flags: "); 
            if (ReadPkt.ReadFlags & EVENTLOG_SEQUENTIAL_READ) { 
                printf("Sequential "); 
            } 
            if (ReadPkt.ReadFlags & EVENTLOG_SEEK_READ) { 
                printf("Seek "); 
            } 
            if (ReadPkt.ReadFlags & EVENTLOG_FORWARDS_READ) { 
                printf("Forward "); 
            } 
            if (ReadPkt.ReadFlags & EVENTLOG_BACKWARDS_READ) { 
                printf("Backwards "); 
            } 
            printf("\n"); 
            break; 
 
        case ELF_COMMAND_WRITE: 
            printf("\nWrite packet\n"); 
            if (Request.Flags == ELF_FORCE_OVERWRITE) { 
                printf("with ELF_FORCE_OVERWRITE enabled\n"); 
            } 
            else { 
                printf("\n"); 
            } 
            GET_DATA(Request.Pkt.WritePkt, &WritePkt, sizeof(WRITE_PKT)) 
            RecordSize = (WritePkt.Datasize); 
            DumpRecord((DWORD)WritePkt.Buffer, 0, 0, 0); 
            break; 
 
        case ELF_COMMAND_CLEAR: 
            printf("\nClear packet\n"); 
            GET_DATA(Request.Pkt.ClearPkt, &ClearPkt, sizeof(CLEAR_PKT)) 
            FileName = GetUnicodeString(ClearPkt.BackupFileName); 
            printf("Backup filename = %ws\n", FileName); 
            LocalFree(FileName); 
            break; 
 
        case ELF_COMMAND_BACKUP: 
            printf("\nBackup packet\n"); 
            GET_DATA(Request.Pkt.BackupPkt, &BackupPkt, sizeof(BACKUP_PKT)) 
            FileName = GetUnicodeString(BackupPkt.BackupFileName); 
            printf("Backup filename = %ws\n", FileName); 
            LocalFree(FileName); 
            break; 
 
        case ELF_COMMAND_WRITE_QUEUED: 
            printf("\nQueued Write packet\n"); 
            if (Request.Flags == ELF_FORCE_OVERWRITE) { 
                printf("with ELF_FORCE_OVERWRITE enabled\n"); 
            } 
            else { 
                printf("\n"); 
            } 
            printf("NtStatus = 0x%X\n", Request.Status); 
            break; 
 
        default: 
            printf("\nInvalid packet\n"); 
    } 
 
    printf("\nLogFile for this packet:\n\n"); 
    itoa((DWORD) Request.LogFile, Address, 16); 
    logfile(hCurrentProcess, hCurrentThread, dwCurrentPc, lpExtensionApis, 
        Address); 
 
    printf("\nLogModule for this packet:\n\n"); 
    itoa((DWORD)Request.Module, Address, 16); 
    logmodule(hCurrentProcess, hCurrentThread, dwCurrentPc, lpExtensionApis, 
        Address); 
 
    return; 
} 
 
// 
// Online help 
// 
 
VOID 
help( 
    HANDLE hCurrentProcess, 
    HANDLE hCurrentThread, 
    DWORD dwCurrentPc, 
    PNTSD_EXTENSION_APIS lpExtensionApis, 
    LPSTR lpArgumentString 
    ) 
{ 
    InitFunctionPointers(hCurrentProcess, lpExtensionApis); 
 
    printf("\nEventlog NTSD Extensions\n"); 
 
    if (!lpArgumentString || *lpArgumentString == '\0' || 
        *lpArgumentString == '\n' || *lpArgumentString == '\r') { 
        printf("\tlogmodule - dump a logmodule structure\n"); 
        printf("\tlogfile   - dump a logfile structure\n"); 
        printf("\trequest   - dump a request record\n"); 
        printf("\trecord    - dump a eventlog record\n"); 
        printf("\n\tEnter help <cmd> for detailed help on a command\n"); 
    } 
    else { 
        if (!stricmp(lpArgumentString, "logmodule")) { 
            printf("\tlogmodule <arg>, where <arg> can be one of:\n"); 
            printf("\t\tno argument - dump all logmodule structures\n"); 
            printf("\t\taddress     - dump the logmodule at specified address\n"); 
            printf("\t\t.string     - dump the logmodule with name string\n"); 
        } 
        else if (!stricmp(lpArgumentString, "logfile")) { 
            printf("\tlogfile <arg>, where <arg> can be one of:\n"); 
            printf("\t\tno argument - dump all logfile structures\n"); 
            printf("\t\taddress     - dump the logfile at specified address\n"); 
            printf("\t\t.string     - dump the logfile with name string\n"); 
        } 
        else if (!stricmp(lpArgumentString, "record")) { 
            printf("\trecord <arg>, where <arg> can be one of:\n"); 
            printf("\t\tno argument - dump all records in system log\n"); 
            printf("\t\taddress     - dump records starting at specified address\n"); 
            printf("\t\t.string     - dump all records in the <string> log\n"); 
            printf("\t\t#<nnn>      - dumps records starting at nnn in system log\n"); 
            printf("\t\t#<nnn> .string  - dumps records starting at nnn in <string> log\n"); 
        } 
        else if (!stricmp(lpArgumentString, "request")) { 
            printf("\trequest - dump the request record at specified address\n"); 
        } 
        else { 
            printf("\tInvalid command [%s]\n", lpArgumentString); 
        } 
    } 
}