Часто возникает ситуация, когда необходимо мониторить и логировать обращения к некоторому значению памяти. Используя WinDbg, я часто использую аппаратную точку останова вида:
В результате получаем какая нить какое значение записывала по целевому адресу. Плюс полезным подспорьем выступает стек вызовов.
Совсем недавно я разгребал подобные логи, целевой код для которых выглядел примерно следующим образом:
for (; ; ) { if (!InterlockedCompareExchange(pBarrier, TRUE, FALSE)) break; WaitForBarrier(); } // ... InterlockedExchange(pBarrier, TRUE); SignalBarrier()
Не секрет, что InterlockedCompareExchange(...) реализуется процессорной инструкцией CMPXCHG с префиксом LOCK. Соответсвенно, для правильной интерпретации логов, возник вопрос: будет ли срабатывать аппаратная точка останова, если после выполнения CMPXCHG значение памяти не меняется (было TRUE).
Если прочесть документацию от Intel, то там есть интересная ремарка:
Получается, что запись происходит всегда. Что бы это проверить, набросаем небольшой тест:
struct CCmpXchgData { CHandle m_hThreadStatedEvent; CHandle m_hStartBarrierEvent; volatile long m_nDest; CCmpXchgData() : m_nDest(0) {} }; // ---------------------------------------------------------------------------- static DWORD CmpXchgExceptionFilter( __in DWORD dwExceptionCode ) { if (EXCEPTION_SINGLE_STEP == dwExceptionCode) { _tprintf(_T(" *** EXCEPTION_SINGLE_STEP occurred\n")); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } // ---------------------------------------------------------------------------- static DWORD WINAPI CmpXchgThreadProc( __inout PVOID pParam ) { CCmpXchgData *pCmpXchgData = reinterpret_cast<CCmpXchgData *>(pParam); VERIFY(::SetEvent(pCmpXchgData->m_hThreadStatedEvent)); VERIFY( WAIT_OBJECT_0 == ::WaitForSingleObject(pCmpXchgData->m_hStartBarrierEvent, INFINITE) ); __try { _tprintf( _T("lock cmpxchg %u <- 1\n"), InterlockedCompareExchange(&pCmpXchgData->m_nDest, 1, 0)); _tprintf( _T("lock cmpxchg %u <- 1\n"), InterlockedCompareExchange(&pCmpXchgData->m_nDest, 1, 0)); } __except( CmpXchgExceptionFilter(GetExceptionCode()) ) { } return ERROR_SUCCESS; } // ---------------------------------------------------------------------------- INT _tmain( IN INT nArgCount, IN PTSTR arrArguments[] ) { CCmpXchgData CmpXchgData; CmpXchgData.m_hThreadStatedEvent.Attach( ::CreateEvent(NULL, TRUE, FALSE, NULL)); if (!CmpXchgData.m_hThreadStatedEvent) return PrintFormatError(_T("Create start event")); CmpXchgData.m_hStartBarrierEvent.Attach( ::CreateEvent(NULL, TRUE, FALSE, NULL)); if (!CmpXchgData.m_hStartBarrierEvent) return PrintFormatError(_T("Create barrier event")); CHandle hCmpXchgThread; hCmpXchgThread.Attach( ::CreateThread(NULL, 0, &CmpXchgThreadProc, &CmpXchgData, 0, NULL) ); if (!hCmpXchgThread) return PrintFormatError(_T("Create test thread")); VERIFY( WAIT_OBJECT_0 == ::WaitForSingleObject(CmpXchgData.m_hThreadStatedEvent, INFINITE) ); if ((DWORD)-1 == ::SuspendThread(hCmpXchgThread)) return PrintFormatError(_T("Suspend test thread")); CONTEXT ThreadContext; ThreadContext.ContextFlags = CONTEXT_ALL; if (!::GetThreadContext(hCmpXchgThread, &ThreadContext)) return PrintFormatError(_T("Get test thread context")); ThreadContext.Dr0 = reinterpret_cast<ULONG_PTR>(&CmpXchgData.m_nDest); ThreadContext.Dr7 = 0x00010501; if (!::SetThreadContext(hCmpXchgThread, &ThreadContext)) return PrintFormatError(_T("Set test thread context")); if ((DWORD)-1 == ::ResumeThread(hCmpXchgThread)) return PrintFormatError(_T("Suspend test thread")); VERIFY(::SetEvent(CmpXchgData.m_hStartBarrierEvent)); VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(hCmpXchgThread, INFINITE)); return ERROR_SUCCESS; }
Тест использует механизм аппаратных точек останова, манипулируя DR-регистрами. Отладочные исключения в обрабатываются механизмом SEH'а. Запустим собранный код теста, получим результаты:
Результаты недвусмысленно подтверждают, что даже если по результату выполнения CMPXCHG память не изменила своего значения, происходит отладочное исключение на запись по целевому адресу. Соответственно, в случае с логами, нужно при анализе отличать первую запись значения от повторной попытки записи.
ΞρεΤΙκ