Часто возникает ситуация, когда необходимо мониторить и логировать обращения к некоторому значению памяти. Используя 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 память не изменила своего значения, происходит отладочное исключение на запись по целевому адресу. Соответственно, в случае с логами, нужно при анализе отличать первую запись значения от повторной попытки записи.
ΞρεΤΙκ