Application Verifier: инфраструктура тестов нештатных путей Windows
· Обновлено: · Го Комура · Разработка Windows, Расследование сбоев, Промышленная камера, Application Verifier, Тестирование нештатных путей, Утечка хендлов
История изменений (1 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619656)
Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.
Го Комура (2026). Application Verifier: инфраструктура тестов нештатных путей Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619656 https://comcomponent.com/ru/blog/2026/03/11/003-application-verifier-abnormal-test-foundation-part2/
- DOI (последняя версия)
- 10.5281/zenodo.21619656
- DOI (эта версия)
- 10.5281/zenodo.21619657
Application Verifier — сильный инструмент, когда нужно заранее вытащить наружу аномалии, которые возникают в нативном коде Windows и на границе Win32. Особенно если нужно тестировать аномалии хендлов, повреждение кучи и failure path при нехватке ресурсов, он довольно быстро поднимает на поверхность проблемы, которых не видно при одном лишь тестировании штатного пути.
В первой части, «Утечка хендлов: почему приложение промышленной камеры падает после месяца работы», мы разобрали случай, когда причиной падения управляющего приложения после долгой работы оказалась утечка хендлов. Но усилить логи — это только половина дела. По-настоящему нужно заранее проверить, останется ли в будущем состояние «понятно, что произошло», если из-за очередной непредвиденной ошибки программиста случится утечка памяти, утечка хендлов, частичный отказ или пропущенное освобождение.
Именно для этого мы использовали Application Verifier. Это инструмент, который на этапе выполнения может вставлять проверки и fault injection в код, работающий в нативном Windows и на границе Win32. Особенно удобно на практике то, что можно заранее вызвать поломки, похожие на нехватку памяти или ресурсов, не выжигая реальную память машины.
Во второй части разберём, что такое Application Verifier, что он умеет и как встроить его в инфраструктуру тестов нештатных путей — в контексте приложения управления промышленной камерой.
Содержание
- Сначала вывод
- Что такое Application Verifier
- 2.1. Если коротко
- 2.2. В каких ситуациях он полезен
- 2.3. В чём практическая польза
- 2.4. Как достать и включить у себя
- Что умеет Application Verifier
- 3.1. Basics: Handles / Heaps / Locks / Memory / TLS и другое
- 3.2. Low Resource Simulation: заранее вызвать нехватку памяти и ресурсов
- 3.3. Page Heap и отладчик
- 3.4.
!avrf/!htrace/ логи
- Зачем мы внедрили его в этот раз
- 4.1. Цель — не только «найти баг»
- 4.2. Вызвать явление, похожее на нехватку памяти
- 4.3. Проверить, можно ли проследить аномалию хендла, когда она случится
- Как вызывать явления, похожие на нехватку памяти и ресурсов
- 5.1. Идея Low Resource Simulation
- 5.2. Что можно заставить отказать
- 5.3. Как применять это на практике
- Как смотреть на аномалии хендлов
- 6.1. Проверка
Handles - 6.2. Смотреть стек open / close через
!htrace - 6.3. Как сочетать это с собственными логами
- 6.1. Проверка
- Как строить инфраструктуру тестов нештатных путей
- 7.1. Перенести единицу запуска в harness
- 7.2. Разделить меню тестов
- 7.3. Что собирать
- 7.4. Критерии приёмки
- 7.5. На что обратить внимание
- Как выбирать, вкратце
- Итог
- Справочные материалы
Карта знаний этой статьи
Application Verifier — инструмент рантайм-проверки user-mode приложений Windows: во время выполнения он следит за вызовами API ОС и управлением ресурсами и сообщает об использовании недействительного хендла и повреждении кучи как verifier stop. Fault injection под именем Low Resource Simulation с заданной вероятностью валит вызовы API, поэтому failure path при нехватке ресурсов можно пройти заранее, не вызывая настоящую нехватку памяти. Если включить проверку Handles, автоматически работает handle tracing: историю хендла смотрят через !htrace в WinDbg, настройки и stop — через !avrf. Целевой EXE нужно настроить до запуска, задним числом включить нельзя. Расследование утечки хендлов в долго работающем приложении нельзя отдавать одному Application Verifier, поэтому на практике сочетают отдельный harness EXE и собственные логи.
flowchart LR
accTitle: Карта знаний: тесты нештатных путей с Application Verifier
accDescr: Рисунок показывает, как Application Verifier через fault injection Low Resource Simulation и handle tracing обнаруживает использование недействительного хендла и повреждение кучи как verifier stop; как причину можно проследить через !avrf и !htrace в WinDbg; и как на практике включают verifier для отдельного harness EXE, а расследование утечки хендлов при долгой работе нельзя отдавать одному Application Verifier без собственных логов.
application_verifier["Application Verifier"]
abnormal_path_testing["тестирование нештатных путей"]
fault_injection["fault injection"]
low_resource_simulation["Low Resource Simulation"]
handle_tracing["handle tracing"]
windbg["WinDbg"]
invalid_handle["использование недействительного хендла"]
heap_corruption["повреждение кучи (heap corruption)"]
page_heap["Page Heap"]
verifier_stop["verifier stop"]
avrf_command["!avrf (расширение WinDbg)"]
harness_exe_pattern["паттерн harness EXE"]
handle_leak["утечка хендлов"]
abnormal_path_testing -.->|"использует"| application_verifier
application_verifier -->|"использует"| fault_injection
low_resource_simulation -->|"использует"| fault_injection
application_verifier -->|"использует"| low_resource_simulation
application_verifier -.->|"использует"| handle_tracing
handle_tracing -->|"проверяется"| windbg
invalid_handle -->|"проверяется"| application_verifier
heap_corruption -->|"проверяется"| application_verifier
heap_corruption -->|"проверяется"| page_heap
verifier_stop -->|"проверяется"| avrf_command
invalid_handle -->|"может вызвать"| verifier_stop
heap_corruption -->|"может вызвать"| verifier_stop
harness_exe_pattern -->|"рекомендуется для"| application_verifier
application_verifier -->|"не рекомендуется"| handle_leak
avrf_command -.->|"требует"| windbg
low_resource_simulation -->|"настраивается"| application_verifier
harness_exe_pattern -->|"рекомендуется для"| abnormal_path_testing
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 17, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
1. Сначала вывод
- Application Verifier — инструмент, который упрощает выявление во время выполнения неправильного использования на неуправляемой / нативной границе Windows
- Ценность не только в том, чтобы «найти баг», но и в том, чтобы заранее вызвать редко проявляющиеся нештатные пути
Handlesловит invalid handle,Heapsпроявляет повреждение кучи,Low Resource Simulationделает fault injection ситуаций, похожих на нехватку памяти и ресурсов- Полностью отдать расследование утечки долго работающего EXE одному Application Verifier — плохая идея; реалистичный путь — сочетать его с собственными логами
Handle Countи жизненного цикла ресурсов - В инфраструктуре тестов нештатных путей результаты проще читать, если разделить прогон verifier по штатному пути и прогон с fault injection
- Даже если нужно проверить DLL, Application Verifier включают для тестового EXE, который реально её запускает
Иначе говоря, Application Verifier — это инструмент, который вытаскивает на свет противные баги вокруг native / Win32-границы Windows. Он особенно хорошо стыкуется с миром приложений управления оборудованием, где native SDK, P/Invoke и Win32 API обычно смешаны.
flowchart TB
accTitle: Две роли Application Verifier
accDescr: У Application Verifier две роли — ловить неправильное использование на нативной границе и заранее вызывать редко проявляющиеся нештатные пути. Первое даёт invalid handle и повреждение кучи, второе — fault injection ситуаций, похожих на нехватку памяти.
av["Application Verifier"] --> detect["Ловить неправильное использование на нативной границе"]
av --> inject["Заранее вызывать редкие нештатные пути"]
detect --> d1["invalid handle и повреждение кучи"]
inject --> d2["Внедрение ситуации, похожей на нехватку памяти"]
Рис. 1: Две опоры Application Verifier — «ловить неправильное использование» и «заранее вызывать нештатные пути».
2. Что такое Application Verifier
2.1. Если коротко
Application Verifier — это инструмент рантайм-проверки для user-mode приложений Windows. Он следит, как работающее приложение вызывает API ОС и обращается с ресурсами: ловит подозрительное использование и умеет намеренно вносить отказы.
В отличие от «статического анализа» и «модульных тестов», это инструмент, который показывает, как код ломается, когда данный путь реально пройден. Поэтому он хорошо подходит, чтобы вытащить failure path, невидимые в обычном функциональном тестировании.
flowchart LR
A[Тестовый harness] --> B[Управляющее приложение / обёртка SDK]
B --> C[Application Verifier]
C --> D[Win32 API / native DLL / ресурсы ОС]
C --> E[verifier stop]
C --> F[вывод отладчика]
C --> G[логи AppVerifier]
B --> H[Собственный structured log]
Рис. 2: Application Verifier наблюдает управляющее приложение, которое гоняет тестовый harness, и оставляет результат как verifier stop, вывод отладчика и логи.
2.2. В каких ситуациях он полезен
Особенно хорошо он работает в таких ситуациях.
- вызываете native DLL или SDK камеры;
- пересекаете границы P/Invoke или COM;
- прямо или косвенно много используете хендлы, кучу, блокировки, виртуальную память;
- на обычном штатном пути почти не падает, но на нештатных путях управление временем жизни выглядит хрупким;
- «изредка возвращает странный отказ» проявляется раньше, чем «падает».
И наоборот, это не инструмент для того, чтобы ходить по графу объектов в чисто управляемом мире. Поэтому даже в C#-приложении он сильно помогает, если граница native SDK или Win32 толстая, но одним им утечку чисто управляемой кучи целиком не разобрать.
flowchart TB
accTitle: Когда Application Verifier полезен, а когда нет
accDescr: Если проблема на границе native SDK или Win32, Application Verifier полезен. Если нужно ходить только по графу объектов в чисто управляемом мире, это уже другой инструмент.
q{"На каком слое проблема?"}
q -->|"native SDK или граница Win32"| yes["Application Verifier полезен"]
q -->|"Чисто управляемый object graph"| no["Не тот инструмент (смотреть другим)"]
Рис. 3: Развилка — толщина native / Win32-границы. Это не инструмент, чтобы смотреть только управляемый мир.
2.3. В чём практическая польза
На практике польза сводится примерно к трём пунктам.
- Быстро останавливать неправильное использование на нативной границе
- invalid handle
- повреждение кучи
- неправильное использование блокировок
- неправильное использование API виртуальной памяти и т. д.
- Заранее вызывать поломки, которые проявляются только при нехватке ресурсов
- эквиваленты
mallocизредка отказывают; CreateEventиCreateFileизредка отказывают;- отказывает
VirtualAlloc.
- эквиваленты
- Проще расследовать в связке с отладчиком
!avrf!htrace!heap -p -a- логи verifier stop
В приложениях управления оборудованием мешает именно «непонятно, что произошло на нештатном пути». Application Verifier довольно хорошо уменьшает эту «непонятность».
flowchart TB
accTitle: Три практические выгоды
accDescr: Быстро останавливать неправильное использование на нативной границе, заранее вызывать поломки, которые видны только при нехватке ресурсов, и проще расследовать в связке с отладчиком.
av["Application Verifier"] --> b1["Быстро останавливать неправильное использование"]
av --> b2["Заранее вызывать форму поломки"]
av --> b3["Проще расследовать в отладчике"]
b3 -.-> t["Расширения вроде avrf и htrace"]
Рис. 4: На практике выгода сводится к раннему обнаружению, заранее вызванным нештатным путям и связке с отладчиком.
2.4. Как достать и включить у себя
Сначала закроем вопрос, как собрать инструмент. Без этого весь дальнейший разговор остаётся теорией.
Application Verifier поставляется в составе Windows SDK. В самой Windows его нет, поэтому запускаете установщик SDK и на экране выбора компонентов отмечаете «Application Verifier». Имя исполняемого файла — appverif.exe.
Перед использованием есть три условия.
- пользователь, который запускает, входит в группу Administrators на этой машине;
- ARM64EC не поддерживается;
- проверяемый код — неуправляемый (нативный).
Связь GUI и командной строки удобно держать в голове так.
| Что делает | |
|---|---|
GUI (appverif.exe) |
Пишет в реестр имя целевого EXE и набор включаемых тестов |
Командная строка (appverif -enable ...) |
Пишет ровно ту же настройку реестра командой |
| Во время выполнения | При старте целевого EXE по этой настройке загружается DLL verifier и ставятся хуки Win32 API |
То есть что GUI, что командная строка — делают одно и то же. Для первого ручного раза удобнее GUI, для CI и скриптов — командная строка.
flowchart TB
accTitle: Связь GUI и командной строки
accDescr: И GUI, и командная строка только пишут одну и ту же настройку в реестр. При старте целевого EXE эта настройка читается, загружается DLL verifier и ставятся хуки Win32 API.
gui["GUI (appverif.exe)"] --> reg["Пишет настройку в реестр"]
cli["Командная строка"] --> reg
reg --> boot["Читается при старте целевого EXE"]
boot --> hook["Загрузка DLL verifier и хуки"]
Рис. 5: GUI и командная строка пишут одну и ту же настройку в реестр. Хуки встают при старте целевого EXE.
В GUI: в левой колонке Applications правый щелчок → «Add Application», добавляете целевой EXE, справа в Tests отмечаете, например, Basics, и жмёте «Save». Снимаете так же: в Applications правый щелчок → «Delete Application», затем «Save».
Из этого следуют два важных ограничения.
- Для уже работающего процесса включить задним числом нельзя. Хуки встают при загрузке DLL, поэтому порядок такой: сначала настройка, затем запуск.
- Настройка живёт, пока её явно не удалят. Если «один раз попробовали» и забыли, на этой машине этот EXE будет стартовать под verifier всегда.
Логи обнаружения по умолчанию пишутся в бинарном виде в %USERPROFILE%\AppVerifierLogs; GUI или командной строкой их можно превратить в XML и сводить.
flowchart TB
accTitle: Порядок включения и то, что настройка остаётся
accDescr: Хуки встают при загрузке DLL, поэтому для уже работающего процесса включить задним числом нельзя. Порядок — сначала настройка, затем запуск. Настройка живёт, пока её явно не удалят.
set["Пишем настройку"] --> launch["Запускаем целевой EXE"]
launch --> on["Работает под verifier"]
on --> keep["Настройка живёт, пока не удалят"]
keep -.-> warn["Если забыть, всегда стартует под verifier"]
running["Уже работающий процесс"] -.-> ng["Включить задним числом нельзя"]
Рис. 6: Порядок «сначала настройка, затем запуск» ломать нельзя. Настройка остаётся на машине, пока её явно не удалят.
3. Что умеет Application Verifier
3.1. Basics: Handles / Heaps / Locks / Memory / TLS и другое
Базовый набор Application Verifier — это Basics.
Здесь собраны проверки, которые чаще всего нужны на практике.
| Слой | Что смотрит | Где это нужно в нашем контексте |
|---|---|---|
Handles |
Использование недействительных хендлов | Не наступаем ли на уже закрытый / повреждённый handle |
Heaps |
Повреждение кучи | Вытащить порчу буфера и use-after-free на границе native SDK |
Leak |
Ресурсы, не освобождённые к выгрузке DLL | Тесты короткоживущего harness и случаи с unload |
Locks / SRWLock |
Неправильное использование блокировок | Проверка гонки reconnect и shutdown |
Memory |
Неправильное использование VirtualAlloc / MapViewOfFile и подобного |
Аномалии вокруг больших буферов и разделяемой памяти |
TLS |
Неправильное использование API Thread Local Storage | Подстраховка для нативного кода со сложными границами потоков |
Threadpool |
Согласованность API threadpool и состояния worker | Подспорье, когда много callback и асинхронной обработки |
Суть не в том, чтобы «понять по логам уже после падения», а в том, чтобы остановить подозрительное использование на месте. Для дефектов длительной работы такое опережение очень помогает.
flowchart TB
accTitle: Идея раннего обнаружения в Basics
accDescr: Не читать логи уже после падения, а останавливать подозрительное использование на месте — так дефекты длительной работы всплывают заранее.
use["Подозрительное использование API"] --> basics["Набор проверок Basics"]
basics --> stop["Остановить на месте"]
stop --> early["Проблема всплывает заранее"]
use -.-> later["Раньше читали только после падения"]
Рис. 7: Ценность Basics в том, что «читать после падения» сменяется на «остановить на месте».
3.2. Low Resource Simulation: заранее вызвать нехватку памяти и ресурсов
На практике это особенно удобный кусок. Потому что можно вызвать явление, близкое к нехватке памяти или ресурсов, не выжигая реальный RAM.
Идея простая.
- берём определённый вызов API;
- с определённой вероятностью;
- намеренно заставляем его отказать.
Так можно пройти error path, которые обычно почти никогда не проходятся.
Конкретно так становится легко намеренно вызывать такие явления.
- отказывают
HeapAllocиVirtualAlloc; - отказывает
CreateFile; - отказывает
CreateEvent; - отказывает
MapViewOfFile; - отказывают выделения OLE/COM вроде
SysAllocString.
Это заметно удобнее, чем пытаться реально вызвать нехватку памяти и мучить всю машину. Более того, можно нацелить fault injection на конкретную DLL. Для конфигураций вроде приложений управления оборудованием, где смешаны собственные обёртки и vendor SDK, это весьма практично.
flowchart TB
accTitle: Как устроен Low Resource Simulation
accDescr: Определённый класс вызовов API с заданной вероятностью намеренно завершается отказом. Так можно пройти error path, которые обычно не проходятся. Цель можно сузить до конкретной DLL.
call["Вызов API"] --> judge{"Попали в заданную вероятность?"}
judge -->|"Да"| fail["Намеренно вернуть отказ"]
judge -->|"Нет"| ok["Обработать как обычно"]
fail --> path["На error path, который обычно не проходят"]
path -.-> dll["Можно сузить до конкретной DLL"]
Рис. 8: Low Resource Simulation по сути — fault injection, который с заданной вероятностью валит вызов API.
3.3. Page Heap и отладчик
Для повреждения кучи сильна связка Heaps и page heap.
В частности, full page heap за счёт guard page даёт преимущество — останавливаться близко к моменту повреждения.
Однако это довольно тяжеловесно. Вместо длительного полного перебора удобнее сузиться до сценариев, близких к воспроизведению, и гонять их под отладчиком.
Поэтому реалистичен такой порядок работы.
- сначала широко включить
Basics; - если куча выглядит подозрительно, использовать full page heap;
- если слишком тяжело, опуститься до light page heap;
- для длительных тестов, близких к продакшену, опираться в основном на собственные логи.
В конечном счёте AppVerifier — не волшебная палочка, а инструмент, у которого лезвие меняют под задачу.
flowchart TB
accTitle: Как выбирать page heap
accDescr: Сначала широко включают Basics. Если куча подозрительна, останавливают в момент повреждения через full page heap. Если слишком тяжело — light page heap. Длительные тесты, близкие к продакшену, смотрят в основном своими логами.
s1["Широко включить Basics"] --> s2{"Куча подозрительна?"}
s2 -->|"Да"| s3["Остановить через full page heap"]
s2 -->|"Нет"| s7["Длительные тесты — в основном свои логи"]
s3 --> s4{"Слишком тяжело?"}
s4 -->|"Да"| s5["Опуститься до light page heap"]
s4 -->|"Нет"| s6["Локально воспроизвести под отладчиком"]
Рис. 9: Page heap не держат включённым всегда. Сначала широко Basics, потом меняют лезвие под задачу.
3.4. !avrf / !htrace / логи
Сначала одно слово. verifier stop, который уже несколько раз встречался, — это событие обнаружения: Application Verifier решил, что «так использовать нельзя». Это не просто строка лога: если процесс идёт под отладчиком, он останавливается на месте. У stop есть номер, он показывается как VERIFIER STOP 00000300. Одни stop можно продолжить, другие — нельзя (остаётся только завершить процесс).
Application Verifier не заканчивает работу, выдав stop. Есть расширения отладчика и логи, поэтому проще понять, что произошло.
!avrf- смотреть текущие настройки verifier и stop, который происходит сейчас;
!htrace- смотреть стеки open / close / обращения к недействительному хендлу;
!heap -p -a- в связке с page heap идти по повреждённому блоку кучи;
- логи AppVerifier
- можно сохранить логи на момент возникновения stop.
Особенно удобно, что при включении Handles автоматически включается handle tracing.
Тогда потом проще выяснить, где этот хендл открыли и где закрыли.
flowchart TB
accTitle: От verifier stop к расследованию
accDescr: При обнаружении неправильного использования выходит verifier stop с номером. Под отладчиком процесс останавливается на месте. Настройки и stop смотрят через avrf, историю хендла — через htrace.
bad["Обнаружено неправильное использование"] --> stop["verifier stop (с номером)"]
stop --> brk["Под отладчиком — останов"]
brk --> avrf["avrf: настройки и stop"]
brk --> ht["htrace: история handle"]
stop -.-> cont["Есть stop, которые можно продолжить, и которые нельзя"]
Рис. 10: verifier stop — не просто строка лога. Под отладчиком это останов на месте и точка входа в расследование.
4. Зачем мы внедрили его в этот раз
4.1. Цель — не только «найти баг»
Цель в этот раз была не просто «найти один баг с помощью AppVerifier». Говоря практичнее, мы хотели проверить следующее.
- если в будущем на каком-то другом failure path снова случится утечка ресурса;
- останется ли в логах нужный контекст;
- сможем ли мы дойти до конца вместе с информацией отладчика;
- не окажемся ли в состоянии «непонятно, что произошло».
Иначе говоря, мы использовали его не только как детектор, но и как тест собственной инфраструктуры наблюдения.
4.2. Вызвать явление, похожее на нехватку памяти
По-настоящему вызвать нехватку памяти на обычной машине разработки довольно хлопотно. Более того, если из-за этого вся машина станет нестабильной, сам тест захлебнётся в шуме.
Поэтому мы пошли через Low Resource Simulation: намеренно наступать на failure path, которые с высокой вероятностью вызывает нехватка памяти или ресурсов.
Это заметно упрощает ответы на такие вопросы.
- если
CreateEventотказывает, остаются ли в логахcameraIdиphase? - действительно ли после половинчатой инициализации выполняется clean up?
- если
VirtualAllocотказывает, не ломает ли состояние повторная попытка? - возвращается ли хендл, если
CreateFileотказывает на пути сохранения?
Хотим подчеркнуть: сама по себе цель — не вызвать аномалию, а сделать так, чтобы форму поломки можно было прочитать, когда она случится.
flowchart TB
accTitle: Проверка инфраструктуры наблюдения через fault injection
accDescr: Low Resource Simulation намеренно проводит через отказ. Проверяют, остаётся ли в логах контекст, выполняется ли зачистка, не ломает ли состояние повтор. Цель — не вызвать аномалию, а убедиться, что форму поломки можно прочитать.
inject["Намеренно пройти через отказ"] --> q1["Остаётся ли в логах контекст?"]
inject --> q2["Выполняется ли clean up?"]
inject --> q3["Не ломает ли повтор?"]
q1 --> goal["Состояние, в котором форму поломки можно прочитать"]
q2 --> goal
q3 --> goal
Рис. 11: Цель fault injection — не вызвать аномалию, а проверить, читаема ли форма поломки, когда она случится.
4.3. Проверить, можно ли проследить аномалию хендла, когда она случится
Как и с утечкой хендлов из первой части, вокруг хендлов место, где всё в итоге упало, и истинная причина легко расходятся.
Поэтому мы хотели подтвердить следующее.
- если выходит stop из-за invalid handle, можно ли через
!htraceпроследить open / close; - связывается ли это с
resourceId/sessionId/phaseв собственных логах; - возвращается ли handle count после отказа;
- легче ли читать разницу утечки, если harness сделать короткоживущим процессом.
Если это видно, можно перейти от простого «баг проявился» к тому, у какой именно ответственности разъехалось управление временем жизни.
flowchart TB
accTitle: Можно ли проследить аномалию хендла
accDescr: Когда выходит invalid handle stop, проверяют, можно ли через htrace проследить open и close, связать это с контекстом собственных логов и увидеть, вернулся ли handle count — и дойти до ответственности, у которой разъехалось время жизни.
stop["invalid handle stop"] --> c1["Через htrace проследить open и close"]
stop --> c2["Связать с контекстом своих логов"]
stop --> c3["Проверить, вернулся ли handle count"]
c1 --> goal["Найти ответственность, у которой разъехалось время жизни"]
c2 --> goal
c3 --> goal
Рис. 12: При аномалии хендла не останавливаться на «баг проявился», а проверять, можно ли дойти до ответственности, у которой разъехалось время жизни.
5. Как вызывать явления, похожие на нехватку памяти и ресурсов
5.1. Идея Low Resource Simulation
Low Resource Simulation — это, по сути, fault injection. Идея не в том, чтобы достоверно воссоздать среду с низкими ресурсами, а в том, чтобы искусственно подмешать типичные отказы API, характерные для нехватки ресурсов.
Поэтому область применения довольно чёткая.
- проверка зачистки на failure path;
- проверка устойчивости retry / reconnect;
- проверка инициализации, где смешаны частичные успехи и частичные отказы;
- проверка того, что логи остаются даже для «отказов, которые обычно никогда не случаются».
Хитрость здесь — не заставлять отказывать всё сразу. Если сразу включить всё подряд, логи взрываются, и становится непонятно, на что вообще смотреть.
flowchart TB
accTitle: Как сужать fault injection
accDescr: Если сразу заставить отказывать всё подряд, логи взрываются и непонятно, на что смотреть. Открывают отказы, близкие к интересующему failure path.
all["Сразу заставить отказывать всё"] --> noise["Логи взрываются и нечитаемы"]
narrow["Открыть только интересующие отказы"] --> clear["Понятно, на что смотрим"]
Рис. 13: Хитрость fault injection — не включать всё сразу, а открывать отказы, близкие к интересующему failure path.
5.2. Что можно заставить отказать
С Low Resource Simulation можно с заданной вероятностью заставлять отказывать примерно такие классы API.
| Класс | Примеры | Пример в приложении управления оборудованием |
|---|---|---|
Heap_Alloc |
Выделение кучи | Временные буферы, метаданные изображения, внутренние выделения в обёртке SDK |
Virtual_Alloc |
Выделение виртуальной памяти | Более крупные буферы кадров, кольцевые буферы |
File |
CreateFile и подобное |
Открытие путей сохранения и файлов логов |
Event |
CreateEvent и подобное |
Уведомление о готовности кадра, синхронизация stop/reconnect |
MapView |
CreateMapView и подобное |
Разделяемая память и memory mapped file |
Ole_Alloc |
SysAllocString и подобное |
Граница COM / OLE |
Wait |
Семейство WaitForXXX |
Вокруг отказов ожидания синхронизации |
Registry |
Обращение к реестру | Чтение и запись настроек, параметры вокруг драйверов |
На практике важно не открывать всё одновременно, а сужаться, начиная с классов, ближе всего к интересующему на этот раз failure path.
5.3. Как применять это на практике
Как набросок командной строки это выглядит, например, так.
appverif /verify CameraHarness.exe
appverif /verify CameraHarness.exe /faults
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 virtual_alloc=20000 file=20000 event=20000
appverif -query lowres -for CameraHarness.exe
Копировать без понимания смысла бессмысленно, поэтому ниже — что делает каждая строка.
| Команда | Что делает |
|---|---|
appverif /verify CameraHarness.exe |
Для CameraHarness.exe включает набор тестов Basics |
appverif /verify CameraHarness.exe /faults |
Плюс к этому включает fault injection. Но целится только в OLE_ALLOC и HEAP_ALLOC |
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 ... |
Включает lowres (Low Resource Simulation) и по отдельности задаёт классы API и вероятности отказа |
appverif -query lowres -for CameraHarness.exe |
Показывает, что сейчас настроено и с какой вероятностью |
appverif /n CameraHarness.exe |
Удаляет настройку этого EXE (тот же смысл, что -disable * -for или -delete settings -for) |
Как читать аргументы, тоже стоит зафиксировать.
- Вероятность задаётся в миллионных долях. Допустимы целые от 0 до 1 000 000,
20000— это20000 / 1 000 000, то есть 2%. Это не «один раз на 20 000 вызовов». В документации Microsoft пример-with registry=20000 file=20000как раз про отказ API реестра и файлов с вероятностью 2%. - После
/faultsможно перечислить вероятность, льготное время и имя DLL. Формат:/faults [вероятность [льготные миллисекунды [DLL ...]]]. Если вероятность опустить, будет 5%, если льготное время опустить — 500 миллисекунд. Льготное время значит «столько после старта процесса fault не вставлять»: иначе падает уже запуск, и пробовать нечего. /n— это снятие.nможно читать примерно как «no verifier». Это парная команда, чтобы не оставлять включение навсегда.
Вывод -query lowres приходит примерно в таком виде. Здесь проверяют, попала ли заданная вероятность и не открыты ли классы, которые не целились.
Settings for CameraHarness.exe:
Test [lowres] enabled.
Include = *
Exclude =
TimeOut = 2000 (0x7D0)
WAIT = 0 (0x0)
HEAP_ALLOC = 20000 (0x4E20)
VIRTUAL_ALLOC = 0 (0x0)
REGISTRY = 0 (0x0)
FILE = 20000 (0x4E20)
EVENT = 20000 (0x4E20)
MAP_VIEW = 0 (0x0)
OLE_ALLOC = 0 (0x0)
STACKS = false
Include и Exclude — сужение целевых модулей, TimeOut — время после старта, в которое fault не вставляют. Значение по умолчанию зависит от способа настройки, поэтому не гадать, а смотреть этот вывод.
Подход примерно такой.
- сначала прогнать штатный путь с одним лишь
Basics; - затем добавить
Low Resource Simulationи прогнать с fault injection; - при необходимости назначить вероятности только тем отказам, которые хотите увидеть, например
fileилиevent; - если нужно нацелиться на конкретную DLL, ограничить injection этой DLL.
Сокращение /faults удобно, но само по себе оно сосредоточено в основном на OLE_ALLOC и HEAP_ALLOC.
Если нужно посмотреть failure path CreateFile или CreateEvent, надёжнее явно прописать -enable lowres -with file=... event=....
В приложениях управления оборудованием часто проще читать результаты, если сузить injection до обёртки камеры или DLL пути сохранения, а не разбрасывать отказы по всему приложению.
flowchart TB
accTitle: Порядок, в котором накладывают fault injection
accDescr: Сначала штатный путь только с Basics, затем добавляют Low Resource Simulation и гоняют с fault injection, задают вероятность только интересующим отказам и при необходимости сужают до конкретной DLL.
s1["Штатный путь только с Basics"] --> s2["Добавить Low Resource и прогнать"]
s2 --> s3["Задать вероятность только интересующим отказам"]
s3 --> s4["При необходимости сузить до конкретной DLL"]
Рис. 14: Не целиться сразу. От штатного пути с Basics постепенно сужают fault injection.
Конкретная запись «сузить до DLL» тоже стоит положить рядом. Третий и последующие аргументы /faults — это целевые модули.
appverif /verify CameraHarness.exe /faults 50000 1000 CameraSdkWrapper.dll
Тогда при старте CameraHarness.exe после 1000 миллисекунд от запуска отказывать с вероятностью 5% (50000 / 1 000 000) будут только операции, начавшиеся из CameraSdkWrapper.dll. Имя модуля пишут с расширением, путь не ставят. Кроме .dll можно указать и загружаемый модуль вроде .ocx.
Сработало ли сужение, смотрят по строкам Include и Exclude в appverif -query lowres -for CameraHarness.exe. Если там по-прежнему *, целью всё ещё является весь процесс.
flowchart TB
accTitle: Как работает fault injection, суженный до DLL
accDescr: В аргументах faults задают вероятность, льготное время и целевой модуль. После льготного времени от старта отказывают только операции, начавшиеся из указанной DLL. Сужение проверяют по Include и Exclude в query.
arg["Задать вероятность, льготное время и имя DLL"] --> grace["После старта льготное время injection нет"]
grace --> target["Отказывают только операции из указанной DLL"]
target --> check["Проверить по Include и Exclude в query"]
Рис. 15: Fault injection, суженный до DLL, после льготного времени валит только операции, начавшиеся из указанного модуля.
Если процесс идёт под отладчиком, диапазон можно менять уже на ходу.
!avrf -trg dll CameraSdkWrapper.dll
!avrf -skp dll VendorSdk.dll
-trg — «целиться сюда», -skp — «это пропустить». Через !avrf -flt смотрят текущие настройки fault injection, через !avrf -flt stacks 10 — стеки недавних внедрённых отказов.
Например, можно собрать такие сценарии.
- отказ
CreateEventсразу после начала reconnect; - отказ
CreateFileпри начале сохранения; - отказ выделения временного буфера;
- отказ
SysAllocStringпри преобразовании COM; - проверка путей отказа API ожидания.
Обычным тестированием штатного пути сюда почти никогда не попасть. Именно поэтому намеренно наступать на них оправданно.
6. Как смотреть на аномалии хендлов
6.1. Проверка Handles
Для всего, что связано с хендлами, начинают с Handles.
Это упрощает ловлю использования недействительных хендлов.
Типично он ловит такие происшествия.
- повторное использование уже закрытого handle;
- передачу повреждённого значения handle;
- использование handle, который так и не инициализировали из-за частичного отказа;
- обращение из другого потока при разъехавшемся времени жизни.
Там, где долгая работа показала бы лишь «изредка появляется странная ошибка», под verifier можно остановиться прямо на месте. Это опережение помогает очень сильно.
flowchart TB
accTitle: Какие происшествия ловит проверка Handles
accDescr: Повторное использование уже закрытого хендла, повреждённое значение, неинициализированный хендл после частичного отказа, неправильное использование из другого потока из-за разъехавшегося времени жизни — под verifier это останавливается на месте.
a1["Повторное использование после close"] --> stop["verifier stop на месте"]
a2["Повреждённое значение handle"] --> stop
a3["Неинициализированный handle"] --> stop
a4["Неправильное использование из-за разъехавшегося времени жизни"] --> stop
Рис. 16: Происшествия, которые при долгой работе выглядят лишь как «изредка странная ошибка», проверка Handles останавливает на месте.
6.2. Смотреть стек open / close через !htrace
Handles ценен ещё и тем, что хорошо стыкуется с handle tracing.
Дальше нужен отладчик, поэтому сначала ставят WinDbg. Он распространяется как Debugging Tools for Windows и ставится из того же установщика Windows SDK, что и Application Verifier.
windbg -xd av -xd ch -xd sov CameraHarness.exe
!avrf
!htrace 0x00000ABC
Параметры первой строки — не заклинание. Application Verifier при обнаружении бросает три вида исключений.
| Параметр | Исключение | Когда выходит |
|---|---|---|
av |
Нарушение доступа (0xC0000005) |
Обнаружено переполнение буфера кучи |
ch |
Недействительный хендл (0xC0000008) |
Обнаружено использование invalid handle |
sov |
Переполнение стека (0xC00000FD) |
Решено, что начального стека не хватает |
А -xd значит ловить это исключение на second chance. First chance обрабатывает сам Application Verifier, собирая информацию stop, поэтому если отладчик вмешался раньше, это мешает. Если отладчик уже запущен, то же самое делают команды sxd av, sxd ch, sxd sov.
Через !htrace обычно хотят увидеть следующее.
- где этот хендл открыли;
- где его закрыли;
- использовали ли его как недействительный хендл;
- не накапливается ли open больше ожидаемого.
Как это выглядит на экране, тоже стоит показать. Ниже — пример вывода из официальной документации, не из нашей среды; форма та же.
Если наступить на invalid handle, сначала выходит так.
Invalid handle - code c0000008 (first chance)
===================================================
VERIFIER STOP 00000300: pid 0x558: invalid handle exception for current stack trace
C0000008 : Exception code.
0012FBF8 : Exception record. Use .exr to display it.
0012FC0C : Context record. Use .cxr to display it.
00000000 :
===================================================
This verifier stop is continuable.
After debugging it use 'go' to continue.
===================================================
Если сразу дать !avrf, видно, что сейчас включено и какой stop произошёл. Последняя строка — краткое резюме.
0:000> !avrf
Global flags: 00000100
Application verifier global flag is set.
Application verifier settings (00000004):
- no heap checking enabled!
- handle checks
Page heap is not active for this process.
Current stop 00000300 : c0000008 0012fbf8 0012fc0c 00000000 .
Using an invalid handle (either closed or simply bad).
История этого хендла через !htrace выстраивает OPEN / CLOSE / BAD REFERENCE — каждый со стеком.
0:000> !htrace 7DC
--------------------------------------
Handle 0x7DC - BAD REFERENCE:
0x801902BE: ntoskrnl!NtSetEvent+0x6C
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - CLOSE:
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - OPEN:
0x77DE265C: KERNEL32!CreateEventA+0x66
0x010011A0: badhandle!main+0x20
--------------------------------------
Читать прямо: если после CLOSE идёт BAD REFERENCE, закрытый хендл использовали ещё раз. По стеку OPEN видно и место, где хендл создали.
Утечки хендлов и неправильное использование неприятны тем, что API, на котором всё в итоге упало, не является истинной причиной.
С !htrace историю этого хендла можно проследить довольно конкретно.
flowchart TB
accTitle: Как читать историю хендла через htrace
accDescr: htrace выстраивает OPEN, CLOSE и BAD REFERENCE — каждый со стеком. Если после CLOSE идёт BAD REFERENCE, закрытый хендл использовали ещё раз. По стеку OPEN видно, где его создали.
open["OPEN (где создали)"] --> close["CLOSE (где закрыли)"]
close --> bad["BAD REFERENCE"]
bad --> mean["Значит, закрытый handle использовали ещё раз"]
open -.-> stack["У каждой записи есть стек"]
Рис. 17: Читать htrace прямо: если после CLOSE стоит BAD REFERENCE, закрытый хендл использовали ещё раз.
6.3. Как сочетать это с собственными логами
Тем не менее одного Application Verifier недостаточно. В частности, вести расследование утечки долго работающего EXE только на нём одном довольно тяжело.
Поэтому на практике сочетают следующее.
- периодический
Handle Count; sessionId;resourceId;phase;- логи жизненного цикла create/open и close/dispose;
- дампы и вывод отладчика в момент verifier stop.
Тогда можно идти, например, так.
- heartbeat показывает, что наклон
Handle Countподозрителен; - логи жизненного цикла сужают круг до ресурса, у которого есть
Create, но нетClose; - прогон verifier заранее выявляет invalid handle или неправильное использование;
!htraceпоказывает стеки open / close.
Эта комбинация заметно упрощает расследование.
flowchart TB
accTitle: Порядок связки своих логов и verifier
accDescr: По heartbeat замечают наклон Handle Count, по lifecycle log сужают ресурс без Close, прогоном verifier заранее вытаскивают неправильное использование, через htrace смотрят стеки open и close.
s1["Заметить наклон Handle Count"] --> s2["Сузить ресурс по lifecycle log"]
s2 --> s3["Прогоном verifier заранее вытащить неправильное использование"]
s3 --> s4["Смотреть стек через htrace"]
Рис. 18: Наклон ловят своими логами, неправильное использование — verifier. Связывают в таком порядке.
7. Как строить инфраструктуру тестов нештатных путей
7.1. Перенести единицу запуска в harness
Application Verifier нельзя включить задним числом для уже работающего процесса. Сначала настройка, затем запуск.
К тому же настройка живёт, пока её явно не удалят. Поэтому на практике удобнее целиться в тестовый harness EXE, а не в само продакшен-приложение.
Например, такая конфигурация.
flowchart LR
A[Scenario Runner] --> B[CameraHarness.exe]
B --> C[CameraSdkWrapper.dll]
C --> D[Vendor SDK]
B --> E[Structured Log]
B --> F[Дамп / отладчик]
Рис. 19: Конфигурация harness: один сценарий — один процесс. Цель verifier — не DLL, а harness EXE, который её запускает.
Это даёт такие преимущества:
- можно гонять по одному сценарию на процесс;
- разницу утечки легко видеть;
- легко переключать включение и выключение настроек AppVerifier;
- при проверке DLL можно работать со стороны EXE.
Команды выглядят так.
appverif /verify CameraHarness.exe
appverif /n CameraHarness.exe
/verify включает Basics, /n удаляет настройку (см. также таблицу в 5.3).
Включение — до запуска, снятие — явное.
Если гонять это, исходя из harness, легче избежать и происшествий с настройками.
7.2. Разделить меню тестов
В инфраструктуре тестов нештатных путей лучше не делать всё за один прогон. Разделение примерно на три направления делает результаты понятнее.
- Штатный путь + Basics
- не вносят ни одного отказа;
- подтверждают, что verifier stop не возникает.
- Направление fault injection
Low Resource Simulation;- целенаправленно вызывают отказы
event/file/heap_alloc/virtual_allocи подобных.
- Направление углублённого анализа кучи
Heaps;- full page heap;
- воспроизводят локально под отладчиком.
Такое разделение не даёт смешаться «ломается ли это при обычном использовании» и «ломается ли это только при нехватке ресурсов».
Наличие или отсутствие fault injection особенно сильно меняет проходимые пути кода. Поэтому стоит гонять и прогон без fault, и прогон с fault.
flowchart TB
accTitle: Меню тестов из трёх направлений
accDescr: Тесты делят на три направления: штатный путь плюс Basics без внедрения отказов, fault injection через Low Resource Simulation и углублённый анализ кучи с full page heap под отладчиком.
menu["Как гонять тесты нештатных путей"] --> m1["Штатный путь и Basics"]
menu --> m2["Направление fault injection"]
menu --> m3["Углублённый анализ кучи"]
m1 -.-> p1["Подтвердить, что stop не возникает"]
m2 -.-> p2["Внедрить целевой отказ"]
m3 -.-> p3["Локально воспроизвести под отладчиком"]
Рис. 20: Не делать всё за один прогон. Три направления меню не дают смешать, где именно ломается.
7.3. Что собирать
Как минимум стоит фиксировать следующее.
| Категория | Что нужно |
|---|---|
| Логи приложения | cameraId, sessionId, phase, handleCount, код ошибки |
| Состояние процесса | Handle Count, Private Bytes, Thread Count |
| Информация отладчика | !avrf, !htrace, при необходимости !heap -p -a |
| Дампы | В момент verifier stop или при аварийном завершении |
| Логи AppVerifier | Записи stop, при необходимости экспорт в XML для сведения |
При необходимости логи со стороны AppVerifier тоже можно выгрузить в XML и сводить. Но одних их часто недостаточно, чтобы замкнуть причину, поэтому практичнее читать их вместе с собственными логами.
Сам по себе большой объём логов ничего не стоит. Важно, чтобы причинно-следственную связь можно было восстановить позже.
7.4. Критерии приёмки
Критерий приёмки «не упало» тоже слишком слаб. В этом контексте нам требовалось как минимум следующее.
- в прогоне штатного пути + Basics нет verifier stop;
- даже при fault injection ожидаемые отказы остаются в логах;
- половинчато инициализированные ресурсы аккуратно приводятся в порядок;
- после reconnect / retry
Handle Countвозвращается близко к baseline; - при возникновении verifier stop его можно проследить по
sessionId/phase/ стеку; - ни один отказ не превращается в «непонятно, что произошло».
Здесь важно оценивать «не ломается» и «прослеживаемо, когда сломалось» как отдельные оси.
flowchart TB
accTitle: Две оси критериев приёмки
accDescr: Критерии приёмки делят на ось «не ломается» — нет verifier stop на штатном пути, ресурсы зачищаются — и ось «прослеживаемо, когда сломалось» — ожидаемый отказ остаётся в логе, его можно пройти по контексту и стеку.
pass["Критерии приёмки"] --> a["Не ломается"]
pass --> b["Прослеживаемо, когда сломалось"]
a --> a1["stop не возникает"]
a --> a2["Ресурсы зачищаются"]
b --> b1["Отказ остаётся в логе"]
b --> b2["Можно пройти по стеку"]
Рис. 21: Одного «не упало» мало. «Не ломается» и «можно проследить» оценивают как разные оси.
7.5. На что обратить внимание
Application Verifier весьма удобен, но это не волшебство.
- пути кода, которые реально не прошли, не проверяются;
- full page heap тяжеловесен;
- stop может возникать и внутри стороннего SDK;
- проходимые пути кода заметно отличаются с fault injection и без;
- это не единственный инструмент для расследования утечки чисто управляемой кучи.
Поэтому позиционирование такое.
- наклон долгой работы — собственные логи и счётчики;
- неправильное использование на нативной границе — Application Verifier;
- восстановление причинно-следственной связи при аномалии — structured log + дамп + отладчик.
Такое разделение труда наиболее практично.
flowchart TB
accTitle: Общая картина разделения труда в расследовании
accDescr: Наклон долгой работы смотрят своими логами и счётчиками, неправильное использование на нативной границе — Application Verifier, восстановление причинно-следственной связи при аномалии — structured log, дамп и отладчик.
q1["Наклон долгой работы"] --> t1["Свои логи и счётчики"]
q2["Неправильное использование на нативной границе"] --> t2["Application Verifier"]
q3["Восстановление причинно-следственной связи при аномалии"] --> t3["Логи, дамп и отладчик"]
Рис. 22: Application Verifier — не волшебная палочка. Под то, что хотите увидеть, инструменты разделяют.
8. Как выбирать, вкратце
- Подозреваете недействительный хендл или двойной close
Handles+!htrace
- Подозреваете повреждение кучи / use-after-free
Heaps+ full page heap +!heap -p -a
- Хотите вызвать явление, похожее на нехватку памяти или ресурсов
Low Resource Simulation
- Постепенно ломается при долгой работе
- сначала собственные
Handle Count/Private Bytes/ лог жизненного цикла
- сначала собственные
- Хотите проверить DLL
- включите Application Verifier для harness EXE, который вызывает эту DLL
Если сразу включить всё подряд, обычно получается туман из логов. Намного понятнее подставлять лезвие, ближайшее к интересующему failure path.
9. Итог
Позиционирование Application Verifier — рантайм-проверка native / Win32-границы Windows. С помощью Handles / Heaps / Locks / Memory / TLS / Low Resource Simulation и остального можно заранее провести через редко проявляющиеся failure path.
В этом контексте окупились следующие моменты: аномалии хендлов стало легко прослеживать через !htrace в момент их возникновения; явления, похожие на нехватку памяти и ресурсов, удалось вызывать, не ломая всю машину; и мы смогли подтвердить, действительно ли собственные логи будут полезны в этот момент.
Практический способ прогона: разделить прогон штатного пути + Basics и прогоны с fault injection, подготовить harness EXE и гонять сценарии короткоживущими процессами. Дополнительно сочетать это с собственными логами, дампами и информацией отладчика, а сам наклон долгих утечек смотреть своими счётчиками — таково разделение труда.
Application Verifier — это инструмент, чтобы не ждать редкую аномалию, а выходить ей навстречу.
В приложениях управления оборудованием важно не только не ломаться, но и одинаково важно уметь объяснить, что произошло, когда что-то всё же сломалось. В этом смысле мы считаем его весьма практичным инструментом.
Часть 1: Утечка хендлов: почему приложение промышленной камеры падает после месяца работы
10. Справочные материалы
- Часть 1: Утечка хендлов: почему приложение промышленной камеры падает после месяца работы
- Application Verifier - Overview
- Application Verifier - Testing Applications
- Application Verifier - Tests within Application Verifier
- Application Verifier - Debugging Application Verifier Stops
- Application Verifier - Features
- !htrace (WinDbg)
- !avrf (WinDbg)
- Download Debugging Tools for Windows
- Загрузка Windows SDK
- GetProcessHandleCount function (processthreadsapi.h)
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Утечка хендлов: почему приложение промышленной камеры падает после месяца работы
На примере приложения управления промышленной камерой разбираем, как смотреть на Windows-приложение, которое внезапно падает после длител...
Почему повторная передача TCP останавливает обмен с промышленной камерой и как это диагностировать
Как диагностировать остановку обмена с промышленной камерой на несколько секунд из-за повторной передачи TCP: потеря пакетов, RTO, метки ...
Как читать «использование памяти» в Windows: Working Set, Private Bytes, Commit и файл подкачки
Столбец «Память» в диспетчере задач, Working Set, Private Bytes и Commit — это разные величины. Разбираем связь виртуальной и физической ...
Инцидент не закрывается восстановлением — постмортем для небольшой команды
Если закрыть инцидент после исправления и извинений, тот же сбой повторится. Адаптируем blameless-постмортем для небольшой команды: шабло...
Спящий режим, гибернация и Modern Standby: как не дать долгоживущему приложению остановиться ночью
Разбираем, почему долгоживущее Windows-приложение к утру оказывается остановленным: чем отличаются спящий режим S3, гибернация и Modern S...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Расследование ошибок и долгие сбои
Периодические сбои, диагностика связи, сбои после длительной работы и проверка путей отказа.
Связанные примеры проектов
В этих примерах используется сходный подход к анализу, расстановке приоритетов или переработке.
Инфраструктура тестирования ошибочных сценариев с Application Verifier
Кейс о создании основы для тестирования ошибочных сценариев, которая упрощает последующие расследования.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Расследование ошибок и причин
Application Verifier и инфраструктура тестов нештатных путей — центральная тема расследования дефектов и анализа первопричины: они помогают воспроизвести сбой и дойти до причины.
Технические консультации и ревью дизайна
Если нужно понять, насколько глубоко закладывать в проект тесты нештатных путей и точки наблюдения, это можно разобрать как техническую консультацию и ревью проектирования.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что такое Application Verifier?
- Инструмент рантайм-проверки для user-mode приложений Windows. Он следит, как работающее приложение вызывает API ОС и обращается с ресурсами: ловит подозрительное использование вроде invalid handle или повреждения кучи и умеет намеренно вносить отказы. В отличие от статического анализа и модульных тестов, это инструмент, который показывает, как код ломается, когда данный путь реально пройден. Поэтому он хорошо подходит, чтобы вытащить failure path, невидимые в обычном функциональном тестировании.
- Можно ли с помощью Application Verifier воспроизвести нехватку памяти?
- С Low Resource Simulation можно заранее вызвать явление, близкое к нехватке памяти или ресурсов, не выжигая реальный RAM машины. Механизм — fault injection: вызовы API вроде HeapAlloc, VirtualAlloc, CreateFile, CreateEvent намеренно завершаются отказом с заданной вероятностью. Можно нацелить отказы и на конкретную DLL, поэтому это удобно и в конфигурации, где смешаны собственная обёртка и vendor SDK. Если сразу заставить отказывать всё подряд, логи станет невозможно читать, поэтому начинают с того, что ближе к интересующему failure path.
- Можно ли использовать Application Verifier для расследования утечки хендлов?
- Если включить проверку Handles, можно ловить использование недействительных хендлов — например, повторное использование уже закрытого хендла. При этом автоматически включается handle tracing, и через !htrace можно посмотреть стек open / close этого хендла. Но полностью отдать расследование утечки долго работающего EXE одному Application Verifier нереалистично. На практике его сочетают с периодической записью Handle Count и собственными логами жизненного цикла ресурсов: наклон ловят своими логами, неправильное использование — verifier.
- Как использовать Application Verifier для тестирования DLL?
- Application Verifier включают для тестового EXE, который реально запускает эту DLL. Включить его для уже работающего процесса задним числом нельзя: сначала настройка, затем запуск. Настройка сохраняется, пока её явно не удалят, поэтому удобнее целиться в тестовый harness EXE, а не в само продакшен-приложение. Если гонять один сценарий на один процесс, разницу утечки легче увидеть, а включение и выключение настройки — легче переключать.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.