Windows Mobile: Some logging tools

If you ever had a windows mobile device that behaved strange you might need some logging to find the cause.

Here is a collection of logging tools I wrote:

BatteryLog

logs the status of battery and power management changes (ie Suspend/Resume etc.)

Instead of just querying the PowerStatusEx the code uses a thread to watch for PowerNotifications

...
    // Request power notifications 
    HANDLE hPowerNotifications = RequestPowerNotifications(hPowerMsgQ,
                                                           PBT_TRANSITION | 
                                                           PBT_RESUME | 
                                                           PBT_POWERINFOCHANGE);
    if (NULL == hPowerNotifications) 
    {
        RETAILMSG(1, (L"RequestPowerNotifications failed: %x\n", GetLastError()));
        goto Error;
    }

    HANDLE rgHandles[2] = {0};
    rgHandles[0] = hPowerMsgQ;
    rgHandles[1] = g_hEventShutDown;

    // Wait for a power notification or for the app to exit
    while(WaitForMultipleObjects(2, rgHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
    {
        DWORD cbRead;
        DWORD dwFlags;
        POWER_BROADCAST *ppb = (POWER_BROADCAST*) new BYTE[cbPowerMsgSize];
        TCHAR* szPPB = new TCHAR[MAX_PATH];  
        memset(szPPB, 0, MAX_PATH * 2);
        TCHAR* szPBtype=new TCHAR[MAX_PATH];
        memset(szPBtype, 0, MAX_PATH * 2);
        TCHAR szOut[MAX_PATH];
        // loop through in case there is more than 1 msg 
        while(ReadMsgQueue(hPowerMsgQ, ppb, cbPowerMsgSize, &cbRead, 
                           0, &dwFlags))
        {
            wsprintf(szPPB, L"");
            wsprintf(szPBtype, L"");
            switch (ppb->Message)
            {
                case PBT_POWERSTATUSCHANGE:
                    RETAILMSG(1,(L"Power Notification Message: PBT_POWERSTATUSCHANGE\n"));
                    wsprintf(szPBtype, L"change: ");
                    //Add2Log(L"Power Notification Message: PBT_POWERSTATUSCHANGE\n",TRUE);
                    break;
                case PBT_SUSPENDKEYPRESSED:
                    RETAILMSG(1,(L"Power Notification Message: PBT_SUSPENDKEYPRESSED\n"));
                    wsprintf(szPBtype, L"keypress: ");
                    //Add2Log(L"Power Notification Message: PBT_SUSPENDKEYPRESSED\n",TRUE);
                    break;
...

So the code does not miss changes. But remember, a suspend is signaled at the next resume!

BTmon

logs BT activity of your device. Handles Device connect/disconnect and other events and logs them to a file.

Fortunately MS provides a message queue for BT events too. So the code uses a similar aproach as batteryLog:

void startMsgQueue(HWND hWnd){
    DEBUGMSG(1, (L"Entering msgQueue with hwnd=%i\n", hWnd));

    MSGQUEUEOPTIONS mqOptions;
    memset (&mqOptions, 0, sizeof(mqOptions));

    mqOptions.dwFlags = 0;
    mqOptions.dwSize = sizeof(mqOptions);
    mqOptions.dwMaxMessages = 10;
    mqOptions.cbMaxMessage = sizeof(BTEVENT);
    mqOptions.bReadAccess = TRUE;

    hMsgQ = CreateMsgQueue(NULL, &mqOptions);
    if (! hMsgQ) {
        nclog(L"Error creating message queue.\n");
        goto exit;
    }
    hBTNotif = RequestBluetoothNotifications(
        BTE_CLASS_CONNECTIONS    |
            BTE_CONNECTION            |
            BTE_DISCONNECTION        |
            BTE_ROLE_SWITCH            |
            BTE_MODE_CHANGE            |
            BTE_PAGE_TIMEOUT        |
            BTE_CONNECTION_AUTH_FAILURE    |
        BTE_CLASS_PAIRING    |
            BTE_KEY_NOTIFY    |
            BTE_KEY_REVOKED    |
        BTE_CLASS_DEVICE    |
            BTE_LOCAL_NAME    |
            BTE_COD            |
        BTE_CLASS_STACK        |
            BTE_STACK_UP    |
            BTE_STACK_DOWN    
        , hMsgQ);

    bStop=FALSE;
    CreateThread(NULL, 0, msgThread, hWnd, 0, &threadID);

exit:
    ;
}
...
DWORD msgThread(LPVOID lpParam){
    HWND hWnd = (HWND)lpParam;
    hwndMsg=hWnd;

    DEBUGMSG(1,(L"Waiting for Bluetooth notifications with hwnd=%i...\n", hWnd));
    nclog(L"%s: Waiting for Bluetooth notifications...\n", logDateTime());
    
    TCHAR szMsg[MAX_PATH];
    wsprintf(szMsg, L"\r\n%s: Waiting for Bluetooth notifications...", logDateTime());
    printMsg(szMsg, hWnd);

    BTEVENT btEvent;
    DWORD dwBytesRead;
    DWORD dwFlags;
    BOOL fRet;

    while (FALSE == bStop) {
        DWORD dwWait = WaitForSingleObject (hMsgQ, 5000);//wait up to 5 seconds INFINITE);
        switch (dwWait){
            case WAIT_OBJECT_0:
                // We have got a Bluetooth event!
                dwFlags = 0;
                dwBytesRead = 0;

                fRet = ReadMsgQueue (hMsgQ, &btEvent, sizeof(BTEVENT), &dwBytesRead, 10, &dwFlags);
                if (! fRet) {
                    DEBUGMSG(1,(L"Error - Failed to read message from queue!\n"));
                    //bStop=TRUE;
                } 
                else {
                    dumpBTevent(btEvent, hWnd);
                }
                break;
            case WAIT_TIMEOUT:
...

The ‘problem’ with that is that the events contain different data and every type has to be handled very differently. Here is just a small example:

...
void dumpBTevent(BTEVENT btEvent, HWND hwnd){
    BT_CONNECT_EVENT* cntEvt = NULL;
    BT_DISCONNECT_EVENT* discntEvt=NULL;
    BT_ROLE_SWITCH_EVENT* rolSwitchEvt=NULL;
    BT_MODE_CHANGE_EVENT* btModeChgEvt=NULL;
    BT_LINK_KEY_EVENT* btLnkKeyEvt=NULL;

    TCHAR btAddress[18];
    TCHAR hConn[12];
    TCHAR encMode[5];
    TCHAR linkType[5];
    TCHAR szTemp[MAX_PATH];
    TCHAR szMsg[MAX_PATH];

    wsprintf(szMsg, L"\r\n");

    switch (btEvent.dwEventId){
        case BTE_KEY_NOTIFY:
            wsprintf(szMsg, L"\r\n%s: BTE_KEY_NOTIFY:", logDateTime());
            break;
        case BTE_KEY_REVOKED:
            btLnkKeyEvt = (BT_LINK_KEY_EVENT*)btEvent.baEventData;
            //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            wsprintf(btAddress, L"%s", btAddr2Mac( btLnkKeyEvt->bta, btAddress));
            wsprintf(szMsg, L"\r\n%s: BTE_KEY_REVOKED: mac=%s", logDateTime(), btAddress);
            break;
        case BTE_LOCAL_NAME:
            wsprintf(szMsg, L"\r\n%s: BTE_LOCAL_NAME:", logDateTime());
            break;
...

connMgrLog

log connection manager events and connection state changes.

This logging tool curently does not log to file. The code also uses a notification event supported by the MS ConnMgr API. The background thread receiving the notification message will post another message to update the main window with the current event data:

connStats connectionStates[]={
    {L"CONNMGR_STATUS_UNKNOWN", 0x00},
    {L"CONNMGR_STATUS_CONNECTED", 0x10 },
    {L"CONNMGR_STATUS_SUSPENDED", 0x11 },
    {L"CONNMGR_STATUS_DISCONNECTED", 0x20 },
    {L"CONNMGR_STATUS_CONNECTIONFAILED", 0x21 },
    {L"CONNMGR_STATUS_CONNECTIONCANCELED", 0x22 },
    {L"CONNMGR_STATUS_CONNECTIONDISABLED", 0x23 },
    {L"CONNMGR_STATUS_NOPATHTODESTINATION", 0x24 },
    {L"CONNMGR_STATUS_WAITINGFORPATH", 0x25 },
    {L"CONNMGR_STATUS_WAITINGFORPHONE", 0x26 },
    {L"CONNMGR_STATUS_PHONEOFF", 0x27 },
    {L"CONNMGR_STATUS_EXCLUSIVECONFLICT", 0x28 },
    {L"CONNMGR_STATUS_NORESOURCES", 0x29 },
    {L"CONNMGR_STATUS_CONNECTIONLINKFAILED", 0x2A },
    {L"CONNMGR_STATUS_AUTHENTICATIONFAILED", 0x2B },
    {L"CONNMGR_STATUS_WAITINGCONNECTION", 0x40 },
    {L"CONNMGR_STATUS_WAITINGFORRESOURCE", 0x41 },
    {L"CONNMGR_STATUS_WAITINGFORNETWORK", 0x42 },
    {L"CONNMGR_STATUS_WAITINGDISCONNECTION", 0x80 },
    {L"CONNMGR_STATUS_WAITINGCONNECTIONABORT", 0x81 },
    {NULL, -1},
};

UINT WM_CM_STATUS_CHANGE = WM_USER+1234;
...
void startConnMgrWatch(HWND hWnd){
    HRESULT hr;
    hEdit=hWnd;
    if(WM_CM_STATUS_CHANGE==WM_USER+1234){    // do only once, if 1234 then not registered
        WM_CM_STATUS_CHANGE = RegisterWindowMessage( CONNMGR_STATUS_CHANGE_NOTIFICATION_MSG );
        //DEBUGMSG(1, (L"RegisterWindowMessage =0x%x.\r\n", WM_CM_STATUS_CHANGE));
        logMsg(L"RegisterWindowMessage =0x%x.\r\n", WM_CM_STATUS_CHANGE);
    }
    // after you registered for the CONNMGR_STATUS_CHANGE_NOTIFICATION_MSG and got a constant you can watch for changes
    hr = ConnMgrRegisterForStatusChangeNotification(TRUE, hWnd);
    DEBUGMSG(1, (L"ConnMgrRegisterForStatusChangeNotification =0x%x.\r\n", hr));
    return;
}
...
void dump_details_params(CONNMGR_CONNECTION_DETAILED_STATUS *p){
    //DEBUGMSG(1, (L"\r\n############################################\r\n"));
    logMsg(L"\r\n############################################\r\n");
    //check which flags are valid and dump them
    if(p->dwParams & CONNMGRDETAILEDSTATUS_PARAM_TYPE){            //The dwType member of CONNMGR_CONNECTION_DETAILED_STATUS is valid
        //DEBUGMSG(1,(L"\tConnType= '%s'\r\n", szConnType[p->dwType]));
        logMsg(L"\tConnType= '%s'\r\n", szConnType[p->dwType]);
    }
    if(p->dwParams & CONNMGRDETAILEDSTATUS_PARAM_SUBTYPE){        //dwSubtype member of CONNMGR_CONNECTION_DETAILED_STATUS is valid
        //DEBUGMSG(1,(L"\tConnSubtype= '%s'\r\n", szConnSubtype[p->dwSubtype]));
        logMsg(L"\tConnSubtype= '%s'\r\n", szConnSubtype[p->dwSubtype]);
        switch(p->dwSubtype){
            case CM_CONNSUBTYPE_UNKNOWN:
                break;
        }
    }
    if(p->dwFlags & CONNMGRDETAILEDSTATUS_PARAM_FLAGS){
        if(p->dwFlags && CM_DSF_BILLBYTIME){
            //DEBUGMSG(1, (L"\t cm_flags+=CM_DSF_BILLBYTIME\r\n"));
            logMsg(L"\t cm_flags+=CM_DSF_BILLBYTIME\r\n");
        }
        if(p->dwFlags && CM_DSF_ALWAYSON){
            //DEBUGMSG(1, (L"\t cm_flags+=CM_DSF_ALWAYSON\r\n"));
            logMsg(L"\t cm_flags+=CM_DSF_ALWAYSON\r\n");
        }
...
void dump_details(CONNMGR_CONNECTION_DETAILED_STATUS *p)
{
    dump_details_params(p);
    return;
    TCHAR txt[MAX_PATH];
    if (p->szAdapterName && wcslen(p->szAdapterName)>0){
        //DEBUGMSG(1,(L"adap: %s\r\n", p->szAdapterName));
        logMsg(L"adap: %s\r\n", p->szAdapterName);
        //wsprintf(txt, L"adap: %s\r\n", p->szAdapterName);
        //addText(hWndMain, txt);
    }
    if (p->szDescription){
        //DEBUGMSG(1, (L"desc: %s\r\n", p->szDescription));
        logMsg(L"desc: %s\r\n", p->szDescription);
        //wsprintf(txt, L"desc: %s\r\n", p->szDescription);
        //addText(hWndMain, txt);
    }
...

deviceMon

(work in progress) watches the device for changes in drivers like connect/disconnect etc.

This logging tool will also use a message queue provided by the MS API set. Here is a small sample watching for a disk change:

...
void MonitorForNewDisks()
{
    HANDLE hNotify;
    HANDLE qStore ;
    DWORD flags;
    DWORD size;
    BYTE DevDetail[sizeof(DEVDETAIL) + (MAX_PATH * sizeof( TCHAR ))];
    DEVDETAIL * pDetail = (DEVDETAIL *)DevDetail;
    MSGQUEUEOPTIONS msgopts;

    msgopts.dwSize = sizeof(MSGQUEUEOPTIONS);
    msgopts.dwFlags = 0;
    msgopts.dwMaxMessages = 0;
    msgopts.cbMaxMessage = sizeof(DevDetail);
    msgopts.bReadAccess = TRUE;

    qStore = CreateMsgQueue(NULL, &msgopts);
/*
"IClass"="{f8a6ba98-087a-43ac-a9d8-b7f13c5bae31}"; This is DEVCLASS_STREAM_GUID
In case you follow my suggestion going for CE_DRIVER_SERIAL_PORT_GUID you will need:
"IClass"="{CC5195AC-BA49-48a0-BE17-DF6D1B0173DD}" ; this is CE_DRIVER_SERIAL_PORT_GUID
*/
    hNotify = RequestDeviceNotifications(&BLOCK_DRIVER_GUID, qStore, TRUE);

    do
    {
        if(WaitForSingleObject(qStore, 5000) == WAIT_OBJECT_0)
        {
            while(ReadMsgQueue(qStore, pDetail, sizeof(DevDetail), &size, 1, &flags))
            {
                if(pDetail->fAttached)
                    ;//do something HandleNewDisk(pDetail->szName);
            }
        }
    } while( !StopMonitoringForNewDisks );
...

memuse: logs the memory usage of all running processes on your device. Possibly find a memory leak in an app.

This logging uses a periodic timer to dump the memory use of all processes. Here it usefull to know that there are a max of 32 processes running. Each process has its own slot and we can walk the slots and query the memory use. Then we need to map the process ID to a process name to get a named list:

...
DWORD getVMuse(){
    //build the process list
    int iMax = GetProcessNameList((PROCESSNAMES*) &ProcessNames);

    DWORD total = 0;
    for( int idx = 2; idx < 33; ++idx )
    {
        PROCVMINFO vmi = { 0 };
        if( CeGetProcVMInfo( idx, sizeof( PROCVMINFO ), &vmi ) )
        {
            //DEBUGMSG(1, ( L"%d: 0x08x %d bytes\r\n", idx, vmi.hProc, vmi.cbRwMemUsed ));
            total += vmi.cbRwMemUsed;
            //GetProcessIDFromIndex() and match it to the th32ProcessID member of the PROCESSENTRY32 structure
            HANDLE dwThreaID = GetProcessIDFromIndex(idx);
            for(int i=0; i<iMax; i++){
                if(ProcessNames[i]._dwID == (DWORD)dwThreaID)
                    ProcessNames[i]._memuse = vmi.cbRwMemUsed;
            }
        }
    }
...

showFullScreen

although it is not a real logging tool it can help you to document all windows of a running system. See article at showFullScreen.

All projects can be loaded from logging_ce at github.

One Comment

Leave a Reply