Критические секции
От: Paul Bludov Россия  
Дата: 28.02.02 03:43
Оценка: 640 (14) +2
Статья:
Критические секции
Автор(ы): Павел Блудов
Дата: 14.03.2005
В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки


Авторы:
Paul Bludov

Аннотация:
В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки
Delphi - TMultiReadExclusiveWriteSynchronizer
От: Zilog™ Россия  
Дата: 01.03.02 03:43
Оценка:
В c# есть аналогичные ему ReaderWriterLock.
Don't work hard, work smart.
Re: Критические секции
От: paul_shmakov Россия  
Дата: 28.07.05 09:14
Оценка: 16 (1)
Здравствуйте, Paul Bludov, Вы писали:

PB>В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки


Внутренее устройство поменялось:

Raymond Chen> I hope you weren't using those undocumented critical section fields, because in Windows Server 2003 Service Pack 1, they've changed.
http://blogs.msdn.com/oldnewthing/archive/2005/07/01/434648.aspx

Paul Shmakov
Re[2]: Критические секции
От: Блудов Павел Россия  
Дата: 29.07.05 03:19
Оценка: 9 (1)
Здравствуйте, paul_shmakov, Вы писали:

_>

_>Raymond Chen> I hope you weren't using those undocumented critical section fields, because in Windows Server 2003 Service Pack 1, they've changed.
_>http://blogs.msdn.com/oldnewthing/archive/2005/07/01/434648.aspx


Кстати, по этой ссылке есть ссылка на очень интересную статью:
http://msdn.microsoft.com/msdnmag/issues/03/12/CriticalSections/

Питрек разобрался с 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;


и добавить с класс вот такую функцию:

LPCVOID CriticalSectionCollection::FindLoaderLockDebugAddr()
{
    DWORD64 qwNtDllBase;
    TCHAR    szSystemDir[MAX_PATH];
    SYMBOL_INFO si = {0};
    si.SizeOfStruct = sizeof(SYMBOL_INFO);

    ::GetSystemDirectory(szSystemDir, MAX_PATH);
    if (::SymInitialize(m_hProcess, szSystemDir, false))
    {
        qwNtDllBase = ::SymLoadModule64(m_hProcess, NULL, TEXT("NTDLL.DLL"), NULL, 0, 0);
        if (qwNtDllBase)
        {
            if (!::SymFromName(m_hProcess, "LoaderLockDebug", &si))
                si.Address = 0ULL;

            ::SymUnloadModule64(m_hProcess, qwNtDllBase);
        }
        ::SymCleanup(m_hProcess);
    }

    return (LPCVOID)si.Address;
}


Я не знаю, где Питрек взял эту магическую 0x7ffdf0a0, но в моей системе там мусор. А вот RTL_CRITICAL_SECTION_DEBUG для критической секции Loder'а имеет имя, и имя это LoaderLockDebug. Имея отладочную информацию от ntdll.dll можно легко получить нужный адрес.
Re: Критические секции
От: Dale_ee Эстония www.ammyui.com
Дата: 17.02.06 11:18
Оценка:
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;
}
www.livexaml.com
www.ammyui.com
www.nemerleweb.com
Re[2]: Критические секции
От: Alexey Frolov Беларусь  
Дата: 17.02.06 13:06
Оценка: +1
Здравствуйте, Dale_ee, Вы писали:


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. Если я не прав, автор меня поправит
Re[3]: Критические секции
От: Dale_ee Эстония www.ammyui.com
Дата: 17.02.06 13:15
Оценка:
Здравствуйте, Alexey Frolov, Вы писали:

AF>Если я правильно понял идею, то это не реальный код функции, а всего лишь псевдокод. Т.е это примерный алгоритм, последовательность захвата секции и этот код не может заменить реального вызова функции RtlEnterCriticalSection. Если я не прав, автор меня поправит


Это я понял. Однако псевдокод должен отражать реальный ход событий, поэтому я и интересуюсь, как реализована защита от подобных ситуаций.
www.livexaml.com
www.ammyui.com
www.nemerleweb.com
Re[4]: Критические секции
От: Alexey Frolov Беларусь  
Дата: 17.02.06 14:13
Оценка:
Здравствуйте, Dale_ee, Вы писали:

D_>Это я понял. Однако псевдокод должен отражать реальный ход событий, поэтому я и интересуюсь, как реализована защита от подобных ситуаций.


В таком случае псевдокод превратится в код и потеряет свою наглядность
Re[3]: Критические секции
От: Блудов Павел Россия  
Дата: 22.02.06 02:27
Оценка:
Здравствуйте, Alexey Frolov, Вы писали:


AF>Если я правильно понял идею, то это не реальный код функции, а всего лишь псевдокод. Т.е это примерный алгоритм, последовательность захвата секции и этот код не может заменить реального вызова функции RtlEnterCriticalSection.


+1.

В реальности всё конечно много сложнее: имеются хитрые ходы со SpinCount'ами.
Я их для краткости опустил, да и не только в краткости дело — Rtl*CriticalSection явно писаны на ассемблере и в c++ конструкции так запросто не переводятся.
Re[2]: Критические секции
От: Блудов Павел Россия  
Дата: 22.02.06 02:54
Оценка:
Здравствуйте, Dale_ee, Вы писали:

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_>}


Нет, невозможно. Выделенный жирным код может быть выполнен только в двух случаях:

  1. InterlockedIncrement вернёт 0. Как бы потоки не переключались, 0 получит только один из них. Если это нужно пояснить, пишите.
  2. Отработает RtlpWaitForCriticalSection. Код этой функции в статье не приведён, но что она делает рассказано.
    Код примерно такой:
    static inline VOID RtlpWaitForCriticalSection(LPCRITICAL_SECTION_DBG pcs)
    {
            ::WaitForSingleObject(pcs->LockSemaphore, INFINITE);
    }


    Т.е. Если несколько нитей выполняют WaitForSingleObject, то только одна за раз "оживёт" и продолжит свою работу. Если это нужно пояснить, пишите.
Re[3]: Критические секции
От: lxa http://aliakseis.livejournal.com
Дата: 07.02.09 12:10
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>Кстати, по этой ссылке есть ссылка на очень интересную статью:

БП>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;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.