Цель вопроса: Проверка реальности на Документы MS DllMain
.
Общеизвестно, что в DllMain не стоит слишком много делать, есть определенные вещи, которые нельзя делать, например лучшие практики.
Теперь я наткнулся на новую жемчужину в документации, которая не имеет для меня особого смысла: (эмф. Мой)
When handling
DLL_PROCESS_DETACH
, a DLL should free resources such as heap memory only if the DLL is being unloaded dynamically (thelpReserved
parameter is NULL). If the process is terminating (the lpvReserved parameter is non-NULL), all threads in the process except the current thread either have exited already or have been explicitly terminated by a call to theExitProcess
function, which might leave some process resources such as heaps in an inconsistent state. In this case, it is not safe for the DLL to clean up the resources. Instead, the DLL should allow the operating system to reclaim the memory.
Поскольку глобальные объекты C++ очищаются во время DllMain / DETACH, это будет означать, что глобальные объекты C++ не должны освобождать динамическую память, поскольку куча может находиться в несогласованном состоянии. / Когда DLL «статически связана» с исполняемым файлом. / Конечно, не то, что я вижу там - глобальные объекты C++ (если они есть) различных (наших и сторонних) библиотек выделяют и освобождают в своих деструкторах. (За исключением других ошибок при заказе, и т.д.)
Итак, на какую конкретную техническую проблему направлено это предупреждение?
Поскольку в этом абзаце упоминается завершение потока, может ли быть проблема повреждения кучи, если некоторые потоки не очищены правильно?
API ExitProcess
в целом выполняет следующие функции:
GetProcessHeap()
) через HeapLock
(GetProcessHeap())
(хорошо, конечно, через RtlLockHeap
) (это очень важный шаг для предотвращения тупиковой ситуации)NtTerminateProcess(0, 0)
)LdrShutdownProcess
- внутри этого загрузчика api пройдитесь по списку загруженных модулей и отправьте DLL_PROCESS_DETACH
с ненулевым lpvReserved
.NtTerminateProcess(NtCurrentProcess(), ExitCode )
, который завершит процесс.Проблема здесь в том, что потоки прекращено находятся в произвольном месте. Например, поток может выделять или освобождать память из любой кучи и находиться внутри критического раздела кучи, когда он завершается. В результате, если код во время DLL_PROCESS_DETACH
пытается освободить блок из той же кучи, он заходит в тупик при попытке войти в критический раздел этой кучи (если, конечно, реализация кучи использует это).
Обратите внимание, что это не влияет на кучу основного процесса, потому что мы вызываем HeapLock
, потому что до завершает все потоки (кроме текущего). Цель этого: мы ждем в этом вызове, пока все другие потоки не выйдут из критической секции кучи процесса, и после того, как мы получим критическую секцию, никакие другие потоки не смогут войти в нее, потому что основная куча процесса заблокирована.
Итак, когда мы завершаем потоки после блокировки основной кучи - мы можем быть уверены, что никакие другие потоки, которые были убиты, не находятся внутри критической секции основной кучи или структуры кучи в несогласованном состоянии. Благодаря звонку RtlLockHeap
. Но это касается только кучи основного процесса. Любые другие кучи в процессе не блокируются. Таким образом, эти может находятся в несогласованном состоянии во время DLL_PROCESS_DETACH
или могут быть получены исключительно уже завершенным потоком.
Итак - использование HeapFree
для GetProcessHeap
или утверждение, что LocalFree
безопасен (но не документирован) здесь.
Использование HeapFree
для любых других куч является безопасным с точки зрения нет, если DllMain
вызывается во время завершения процесса.
Также, если вы используете другие настраиваемые структуры данных несколькими потоками - они могут находиться в несогласованном состоянии, потому что другие потоки (которые могут их использовать) завершаются в произвольной точке.
Таким образом, это примечание является предупреждением о том, что, когда параметр lpvReserved равен не NULL (что означает, что DllMain вызывается во время завершения процесса), вам нужно быть особенно осторожным при очистке ресурсов. В любом случае вся внутренняя память будет освобождена операционной системой, когда процесс завершится.
Потрясающий! То, что вы пишете, имеет большой смысл. Могу я узнать, откуда вы знаете, как работает функция ExitProcess
? Это где-то задокументировано?
@MartinBa - это очень просто посмотреть в отладчике. но конечно никакой официальной документации от ms
В качестве дополнения к отличному ответу RbMm я добавлю цитату из ExitProcess
, которая работает намного лучше, чем документация DllMain, объясняя, почему операция кучи (или любая другая операция, на самом деле) может быть скомпрометирована:
If one of the terminated threads in the process holds a lock and the DLL detach code in one of the loaded DLLs attempts to acquire the same lock, then calling
ExitProcess
results in a deadlock. In contrast, if a process terminates by calling TerminateProcess, the DLLs that the process is attached to are not notified of the process termination. Therefore, if you do not know the state of all threads in your process, it is better to callTerminateProcess
thanExitProcess
. Note that returning from the main function of an application results in a call toExitProcess
.
Итак, все сводится к следующему: IFF у вашего приложения есть «беглые» потоки, которые могут удерживать блокировку любой, ярким примером является блокировка кучи (CRT), у вас есть большая проблема во время завершения работы, когда вам нужно получить доступ к тем же структурам ( например, куча), которую используют ваши "беглые" потоки.
Это просто показывает, что вы должны отключать все ваши потоки контролируемым образом.
Такого рода словоблудие добавляется, когда у них слишком много телефонных звонков в службу поддержки. Единственная причина задокументировать недокументированный параметр. Пример сценария: программист, использующий SetUnhandledExceptionFilter (), делает что-то полезное, чтобы помочь диагностировать сбой, а затем ExitProcess вместо TerminateProcess. Всегда работает при тестировании, облом, когда в реальном мире сбой был вызван повреждением кучи, а блокировка кучи все еще удерживается.