Питрек разобрался с RTL_CRITICAL_SECTION_DEBUG, на что я не сподобился.
И утилита просмотра критических секций, прилагаемая к статье очень полезная.
Только нужно маленько подправить код. В CritSect32 вместо
DWORD PEBLoaderLock = 0x7ffdf0a0;
PDWORD pLoaderLockAddress;
// Locate the address of the loader lock critical section in the process.if ( !ReadProcessMemory( m_hProcess, (PVOID) PEBLoaderLock,
&pLoaderLockAddress, sizeof ( PDWORD ), 0) )
return false;
// Load the loader lock critical section into our own buffer.
_RTL_CRITICAL_SECTION csLoaderLock;
if ( !ReadProcessMemory( m_hProcess, pLoaderLockAddress, &csLoaderLock,
sizeof ( _RTL_CRITICAL_SECTION ), 0) )
return false;
// Load the debug structure for the loader lock into our own buffer.
// This is the 3rd debug area in the doubly-linked list.
_RTL_CRITICAL_SECTION_DEBUG csdLoaderLock;
if ( !ReadProcessMemory( m_hProcess, csLoaderLock.DebugInfo,
&csdLoaderLock, sizeof ( _RTL_CRITICAL_SECTION_DEBUG ), 0) )
return false;
нужно написать
// Load the debug structure for the loader lock into our own buffer.
// This is the 3rd debug area in the doubly-linked list.
_RTL_CRITICAL_SECTION_DEBUG csdLoaderLock;
if ( !ReadProcessMemory( m_hProcess, FindLoaderLockDebugAddr(),
&csdLoaderLock, sizeof ( _RTL_CRITICAL_SECTION_DEBUG ), 0) )
return false;
Я не знаю, где Питрек взял эту магическую 0x7ffdf0a0, но в моей системе там мусор. А вот RTL_CRITICAL_SECTION_DEBUG для критической секции Loder'а имеет имя, и имя это LoaderLockDebug. Имея отладочную информацию от ntdll.dll можно легко получить нужный адрес.
VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
if (::InterlockedIncrement(&pcs->LockCount))
{
if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())
{ pcs->RecursionCount++;
return;
}
RtlpWaitForCriticalSection(pcs);
}
//Возможно ли переключение потоков? Это могло бы привести к тому, что два потока захватят одну секцию и тогда один поток останется не у дел. После чего будет полный ЧП, так как вызов Enter/Leave будет явно не парным. pcs->OwningThread = (HANDLE)::GetCurrentThreadId(); pcs->RecursionCount = 1;
}
D_>VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)
D_>{
D_> if (::InterlockedIncrement(&pcs->LockCount))
D_> {
D_> if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())
D_> {
pcs->>RecursionCount++;
D_> return;
D_> }
D_> RtlpWaitForCriticalSection(pcs);
D_> }
D_> //Возможно ли переключение потоков? Это могло бы привести к тому, что два потока захватят одну секцию и тогда один поток останется не у дел. После чего будет полный ЧП, так как вызов Enter/Leave будет явно не парным.
pcs->>OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->>RecursionCount = 1;
D_>}
Если я правильно понял идею, то это не реальный код функции, а всего лишь псевдокод. Т.е это примерный алгоритм, последовательность захвата секции и этот код не может заменить реального вызова функции RtlEnterCriticalSection. Если я не прав, автор меня поправит
Здравствуйте, Alexey Frolov, Вы писали:
AF>Если я правильно понял идею, то это не реальный код функции, а всего лишь псевдокод. Т.е это примерный алгоритм, последовательность захвата секции и этот код не может заменить реального вызова функции RtlEnterCriticalSection. Если я не прав, автор меня поправит
Это я понял. Однако псевдокод должен отражать реальный ход событий, поэтому я и интересуюсь, как реализована защита от подобных ситуаций.
Здравствуйте, Dale_ee, Вы писали:
D_>Это я понял. Однако псевдокод должен отражать реальный ход событий, поэтому я и интересуюсь, как реализована защита от подобных ситуаций.
В таком случае псевдокод превратится в код и потеряет свою наглядность
AF>Если я правильно понял идею, то это не реальный код функции, а всего лишь псевдокод. Т.е это примерный алгоритм, последовательность захвата секции и этот код не может заменить реального вызова функции RtlEnterCriticalSection.
+1.
В реальности всё конечно много сложнее: имеются хитрые ходы со SpinCount'ами.
Я их для краткости опустил, да и не только в краткости дело — Rtl*CriticalSection явно писаны на ассемблере и в c++ конструкции так запросто не переводятся.
D_>VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)
D_>{
D_> if (::InterlockedIncrement(&pcs->LockCount))
D_> {
D_> if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())
D_> {
D_> pcs->>RecursionCount++;
D_> return;
D_> }
D_> RtlpWaitForCriticalSection(pcs);
D_> }
D_> //Возможно ли переключение потоков? Это могло бы привести к тому, что два потока захватят одну секцию и тогда один поток останется не у дел. После чего будет полный ЧП, так как вызов Enter/Leave будет явно не парным.pcs->>OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->>RecursionCount = 1;
D_>}
Нет, невозможно. Выделенный жирным код может быть выполнен только в двух случаях:
InterlockedIncrement вернёт 0. Как бы потоки не переключались, 0 получит только один из них. Если это нужно пояснить, пишите.
Отработает RtlpWaitForCriticalSection. Код этой функции в статье не приведён, но что она делает рассказано.
Код примерно такой:
Здравствуйте, Блудов Павел, Вы писали:
БП>Кстати, по этой ссылке есть ссылка на очень интересную статью: БП>http://msdn.microsoft.com/msdnmag/issues/03/12/CriticalSections/
... БП>Только нужно маленько подправить код.
... БП>Имея отладочную информацию от ntdll.dll можно легко получить нужный адрес.
Еще возможен вариант через "зондовую" критическую секцию, тогда исчезает зависимость от отладочной информации. Код CriticalSectionCollection::CriticalSectionWalk() приведен полностью, чтобы не возникло путаницы, некоторые проверки опущены. Только надо еще добавить прав при открытии m_hProcess для VirtualAllocEx и CreateRemoteThread:
bool CriticalSectionCollection::CriticalSectionWalk()
{
// Create a probe critical section in the process.
PVOID pProbeLockAddress = VirtualAllocEx(m_hProcess, NULL, sizeof(_RTL_CRITICAL_SECTION), MEM_COMMIT, PAGE_READWRITE);
HMODULE hKernel32 = GetModuleHandle ( "kernel32.dll" );
HANDLE hThread = ::CreateRemoteThread( m_hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32, "InitializeCriticalSection" ),
pProbeLockAddress, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// Clean up
::CloseHandle( hThread );
// Load the loader lock critical section into our own buffer.
_RTL_CRITICAL_SECTION csProbeLock;
if ( !ReadProcessMemory( m_hProcess, pProbeLockAddress, &csProbeLock,
sizeof ( _RTL_CRITICAL_SECTION ), 0) )
return false;
// Load the debug structure for the loader lock into our own buffer.
// This is the 3rd debug area in the doubly-linked list.
_RTL_CRITICAL_SECTION_DEBUG csdProbeLock;
if ( !ReadProcessMemory( m_hProcess, csProbeLock.DebugInfo,
&csdProbeLock, sizeof ( _RTL_CRITICAL_SECTION_DEBUG ), 0) )
return false;
// We need to walk the list backwards to the 1st entry.
PRTL_CRITICAL_SECTION_DEBUG pListEntry =
(PRTL_CRITICAL_SECTION_DEBUG)
((DWORD) csdProbeLock.ProcessLocksList.Blink
- (offsetof (_RTL_CRITICAL_SECTION_DEBUG, ProcessLocksList)));
hThread = ::CreateRemoteThread( m_hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32, "DeleteCriticalSection" ),
pProbeLockAddress, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// Clean up
::CloseHandle( hThread );
VirtualFreeEx(m_hProcess, pProbeLockAddress, sizeof(_RTL_CRITICAL_SECTION), MEM_DECOMMIT);
// Now, walk the chain from the end to the beginning.while ( TRUE )
{
// Read the critical section debug structure first...
_RTL_CRITICAL_SECTION_DEBUG cs_debug;
if ( !ReadProcessMemory (m_hProcess, pListEntry, &cs_debug,
sizeof ( _RTL_CRITICAL_SECTION_DEBUG ), 0) )
break;
if (0 == cs_debug.CriticalSection)
break;
// since this will give us the address to the critical section.
RTL_CRITICAL_SECTION cs;
if ( !ReadProcessMemory (m_hProcess, cs_debug.CriticalSection, &cs,
sizeof ( _RTL_CRITICAL_SECTION ), 0 ) )
break;
PCRITICAL_SECTION_ENTRY pcse = new CRITICAL_SECTION_ENTRY;
// Items from the critical section structure.
pcse->LockCount = cs.LockCount;
pcse->RecursionCount = cs.RecursionCount;
pcse->OwningThread = cs.OwningThread;
pcse->LockSemaphore = cs.LockSemaphore;
pcse->SpinCount = cs.SpinCount;
// Items from the critical section debug structure.
pcse->CriticalSection = cs_debug.CriticalSection;
pcse->EntryCount = cs_debug.EntryCount;
pcse->ContentionCount = cs_debug.ContentionCount;
pcse->Spare[0] = cs_debug.Spare[0];
pcse->Spare[1] = cs_debug.Spare[1];
// Save all of the important fields in our own collection.
m_Collection->push_back( pcse );
// The backward link (Blink) actually points into the middle of the
// previous critical section debug structure, so we take its address and
// back up the correct number of DWORDs to obtain the next entry in
// the chain. Use the offsetof macro to let the structures and the
// compiler do the actual work.
pListEntry =
(PRTL_CRITICAL_SECTION_DEBUG)
((DWORD) cs_debug.ProcessLocksList.Blink
- (offsetof (_RTL_CRITICAL_SECTION_DEBUG, ProcessLocksList)));
}
CloseHandle( m_hProcess );
std::reverse(m_Collection->begin(), m_Collection->end());
return true;
}