Windows error reporting wer0 no data

Служба Windows Error Reporting (WER) служит для сбора и отправки отладочной информации о падении системных и сторонних приложений в Windows на сервера Microsoft

Служба Windows Error Reporting (WER) служит для сбора и отправки отладочной информации о падении системных и сторонних приложений в Windows на сервера Microsoft. По задумке Microsoft, эта информация должна анализироваться и при наличии решения, вариант исправления проблемы должен отправляется пользователю через Windows Error Reporting Response. Но по факту мало кто пользуется этим функционалом, хотя Microsoft настойчиво оставляет службу сбора ошибок WER включенной по умолчанию во всех последних версиях Windows. В большинстве случае о службе WER вспоминают, когда каталог C:ProgramDataMicrosoftWindowsWERReportQueue начинает занимать много места на системном диске (вплоть до нескольких десятков Гб), даже не смотря на то что на этом каталоге по умолчанию включена NTFS компрессия.

Содержание:

  • Служба Windows Error Reporting
  • Очистка папки WERReportQueue в Windows
  • Отключение Window Error Reporting в Windows Server
  • Отключаем сбор и отправки отчетов об ошибках в Windows 10
  • Отключение Windows Error Reporting через GPO

C:ProgramDataMicrosoftWindowsWERReportQueue

Служба Windows Error Reporting

Служба Windows Error Reporting при появлении ошибки показывает диалоговое окно, предлагающее отправить отчет об ошибке в корпорацию Microsoft. Когда в Windows вы видите сообщение об ошибке
YourApp has stop working
, в это время в служба Windows Error Reporting запускает утилиту WerFault.exe для сбора отладочных данных (могут включать в себя дамп памяти).

ошиька app stopped working в windows

Данные пользователя сохраняются в профиль пользователя:

%USERPROFILE%AppDataLocalMicrosoftWindowswer

Системные данные – в системный каталог:

%ALLUSERSPROFILE%MicrosoftWindowsWER

Служба Windows Error Reporting представляет собой отдельный сервис Windows. Вы можете проверить состояние службы командой PowerShell:

Get-Service WerSvc

Внутри каталога WERReportQueue содержится множество каталогов, с именами в формате:

  • Critical_6.3.9600.18384_{ID}_00000000_cab_3222bf78
  • Critical_powershell.exe_{ID}_cab_271e13c0
  • Critical_sqlservr.exe__{ID}_cab_b3a19651
  • NonCritical_7.9.9600.18235__{ID}_0bfcb07a
  • AppCrash_cmd.exe_{ID}_bda769bf_37d3b403

Как вы видите, имя каталога содержит степень критичности события и имя конкретного exe файла, который завершился аварийно. Во всех каталогах обязательно имеется файл Report.wer, который содержит описание ошибок и несколько файлов с дополнительной информацией.

Очистка папки WERReportQueue в Windows

Как правило, размер каждой папки в WER незначителен, но в некоторых случаях для проблемного процесса генерируется дамп памяти, который занимает довольно много места. На скриншоте ниже видно, что размер файла дампа memory.hdmp составляет около 610 Мб. Парочка таким дампов – и на диске исчезло несколько свободных гигибайт.

файлы Report.wer и memory.hdmp

Чтобы очистить все эти ошибки и журналы штатными средствами, откройте панель управления и перейдите в раздел ControlPanel -> System and Security -> Security and Maintenance -> Maintenance -> View reliability history -> View all problem reports (Control PanelSystem and SecuritySecurity and MaintenanceProblem Reports) и нажмите на кнопку Clear all problem reports.

очистка ошибок windows error reporing в windows

Для быстрого освобождения места на диске от файлов отладки, сгенерированных службой WER, содержимое следующих каталогов можно безболезненно очистить вручную.

  • C:ProgramDataMicrosoftWindowsWERReportArchive
  • C:ProgramDataMicrosoftWindowsWERReportQueue

Следующие команды PowerShell удалят из каталога каталогов WER все файлы, старше 15 дней:

Get-ChildItem -Path  'C:ProgramDataMicrosoftWindowsWERReportArchive' -Recurse | Where-Object CreationTime -lt (Get-Date).AddDays(-15) | Remove-Item -force -Recurse
Get-ChildItem -Path  'C:ProgramDataMicrosoftWindowsWERReportQueue' -Recurse | Where-Object CreationTime -lt (Get-Date).AddDays(-15) | Remove-Item -force –Recurse

Для очистки каталогов WER в пользовательских профилях используйте такой скрипт:

$users = Get-ChildItem c:users|where{$_.name -notmatch 'Public|default'}
foreach ($user in $users){
Get-ChildItem "C:Users$UserAppDataLocalMicrosoftWindowsWER " –Recurse -ErrorAction SilentlyContinue | Remove-Item –force –Recurse
}

Отключение Window Error Reporting в Windows Server

В Windows Server 2019/2016/2012R2 вы можете управлять состоянием WER с помощью PowerShell. Вы можете отключить службу Windows Error Reporting:

Get-Service WerSvc| stop-service –passthru -force
Set-Service WerSvc –startuptype manual –passthru

Но есть более корректные способы отключения WER в Windows. В версии PowerShell 4.0 добавлен отдельный модуль WindowsErrorReporting из трех командлетов:

Get-Command -Module WindowsErrorReporting

PowerShell модуль WindowsErrorReporting

Проверить состояние службы Windows Error Reporting можно командой:

Get-WindowsErrorReporting

Для отключения WER, выполните:

Disable-WindowsErrorReporting

Disable-WindowsErrorReporting -отключитьWER с помощью PowerShell

В Windows Server 2012 R2 можно отключить запись информации об ошибках Windows Error Reporting через панель управления (Control Panel -> System and Security -> Action Center -> раздел Maintenance -> Settings -> выберите опцию I don’t want to participate, and don’t ask me again

Отключение сбора ошибок службой WER в Windows Server 2012 / R2

Отключаем сбор и отправки отчетов об ошибках в Windows 10

В Windows 10 нельзя отключить Error Reporting через панель управления. В графическогм интерфейсе можно только проверить ее статус (Система и безопасность ->Центр безопасности и обслуживания -> секция Обслуживание). Как вы видите, по умолчанию параметр Поиск решения для указанных в отчетах проблем включен (Control Panel -> System and Security -> Security and Maintenance -> Maintenance -> Report problems = On).

windows10 сбор ошибок WER

HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

Можно отключить сбор ошибок WER для конкретных пользователей:

reg add "HKCUSoftwareMicrosoftWindowsWindows Error Reporting" /v "Disabled" /t REG_DWORD /d "1" /f

Или отключить WER для всех:
reg add "HKLMSoftwareMicrosoftWindowsWindows Error Reporting" /v "Disabled" /t REG_DWORD /d "1" /f

отключить windows error reporting через системный реестр

Измените параметр реестра и проверьте статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

в windows отключен сбор ошибок и отправка в microsoft

Отключение Windows Error Reporting через GPO

Также вы можете управлять настройками службы Windows Error Reporting через групповые политики.

Запустите редактор локальной (
gpedit.msc
) или доменной GPO (
gpmc.msc
) и перейдите в ветку реестра Computer Configuration -> Administrative Templates -> Windows Components -> Windows Error Reporting (Компоненты Windows -> Отчеты об ошибках Windows). Для отключения сбора и отправки ошибок через WER включите политику Disable Windows Error Reporting (Отключить отчеты об ошибках Windows).

Аналогичная политика есть в пользовательском разделе политик (User Configuration).

gpo отключить windows error reporting

Обновите GPO (перезагрузка не потребуется).

В результате в Windows перестанут формироваться сообщения об ошибках Windows и отправляться в Microsoft.

title description ms.author author ms.date ms.topic

Troubleshooting a Failover Cluster using Windows Error Reporting

Troubleshooting a Failover Cluster using WER Reports, with specific details on how to gather reports and diagnose common issues.

johnmar

JohnMarlin-MSFT

10/21/2021

troubleshooting

Troubleshooting a Failover Cluster using Windows Error Reporting

Applies to: Windows Server 2022, Windows Server 2019, Windows Server 2016, Windows Server, Azure Stack HCI, versions 21H2 and 20H2

Windows Error Reporting (WER) is a flexible event-based feedback infrastructure designed to help advanced administrators or Tier 3 support gather information about the hardware and software problems that Windows can detect, report the information to Microsoft, and provide users with any available solutions. This reference provides descriptions and syntax for all WindowsErrorReporting cmdlets.

The information on troubleshooting presented below will be helpful for troubleshooting advanced issues that have been escalated and that may require data to be sent to Microsoft for triaging.

Enabling event channels

When Windows Server is installed, many event channels are enabled by default. But sometimes when diagnosing an issue, we want to be able to enable some of these event channels since it will help in triaging and diagnosing system issues.

You could enable additional event channels on each server node in your cluster as needed; however, this approach presents two problems:

  1. You have to remember to enable the same event channels on every new server node that you add to your cluster.
  2. When diagnosing, it can be tedious to enable specific event channels, reproduce the error, and repeat this process until you root cause.

To avoid these issues, you can enable event channels on cluster startup. The list of enabled event channels on your cluster can be configured using the public property EnabledEventLogs. By default, the following event channels are enabled:

PS C:Windowssystem32> (get-cluster).EnabledEventLogs

Here’s an example of the output:

Microsoft-Windows-Hyper-V-VmSwitch-Diagnostic,4,0xFFFFFFFD
Microsoft-Windows-SMBDirect/Debug,4
Microsoft-Windows-SMBServer/Analytic
Microsoft-Windows-Kernel-LiveDump/Analytic

The EnabledEventLogs property is a multistring, where each string is in the form: channel-name, log-level, keyword-mask. The keyword-mask can be a hexadecimal (prefix 0x), octal (prefix 0), or decimal number (no prefix) number. For instance, to add a new event channel to the list and to configure both log-level and keyword-mask you can run:

(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,2,321"

If you want to set the log-level but keep the keyword-mask at its default value, you can use either of the following commands:

(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,2"
(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,2,"

If you want to keep the log-level at its default value, but set the keyword-mask you can run the following command:

(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,,0xf1"

If you want to keep both the log-level and the keyword-mask at their default values, you can run any of the following commands:

(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic"
(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,"
(get-cluster).EnabledEventLogs += "Microsoft-Windows-WinINet/Analytic,,"

These event channels will be enabled on every cluster node when the cluster service starts or whenever the EnabledEventLogs property is changed.

Gathering Logs

After you have enabled event channels, you can use the DumpLogQuery to gather logs. The public resource type property DumpLogQuery is a mutistring value. Each string is an XPATH query as described here.

When troubleshooting, if you need to collect additional event channels, you can a modify the DumpLogQuery property by adding additional queries or modifying the list.

To do this, first test your XPATH query using the get-WinEvent PowerShell cmdlet:

get-WinEvent -FilterXML "<QueryList><Query><Select Path='Microsoft-Windows-GroupPolicy/Operational'>*[System[TimeCreated[timediff(@SystemTime) &gt;= 600000]]]</Select></Query></QueryList>"

Next, append your query to the DumpLogQuery property of the resource:

(Get-ClusterResourceType -Name "Physical Disk".DumpLogQuery += "<QueryList><Query><Select Path='Microsoft-Windows-GroupPolicy/Operational'>*[System[TimeCreated[timediff(@SystemTime) &gt;= 600000]]]</Select></Query></QueryList>"

And if you want to get a list of queries to use, run:

(Get-ClusterResourceType -Name "Physical Disk").DumpLogQuery

Gathering Windows Error Reporting reports

Windows Error Reporting Reports are stored in %ProgramData%MicrosoftWindowsWER

Inside the WER folder, the ReportsQueue folder contains reports that are waiting to be uploaded to Watson.

PS C:Windowssystem32> dir c:ProgramDataMicrosoftWindowsWERReportQueue

Here’s an example of the output:

Volume in drive C is INSTALLTO
Volume Serial Number is 4031-E397

Directory of C:ProgramDataMicrosoftWindowsWERReportQueue

<date>  <time>    <DIR>          .
<date>  <time>    <DIR>          ..
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_02d10a3f
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_0588dd06
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_10d55ef5
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_13258c8c
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_13a8c4ac
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_13dcf4d3
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_1721a0b0
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_1839758a
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_1d4131cb
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_23551d79
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_2468ad4c
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_255d4d61
<date>  <time>    <DIR>          Critical_Physical Disk_1cbd8ffecbc8a1a0e7819e4262e3ece2909a157a_00000000_cab_08289734
<date>  <time>    <DIR>          Critical_Physical Disk_64acaf7e4590828ae8a3ac3c8b31da9a789586d4_00000000_cab_1d94712e
<date>  <time>    <DIR>          Critical_Physical Disk_ae39f5243a104f21ac5b04a39efeac4c126754_00000000_003359cb
<date>  <time>    <DIR>          Critical_Physical Disk_ae39f5243a104f21ac5b04a39efeac4c126754_00000000_cab_1b293b17
<date>  <time>    <DIR>          Critical_Physical Disk_b46b8883d892cfa8a26263afca228b17df8133d_00000000_cab_08abc39c
<date>  <time>    <DIR>          Kernel_166_1234dacd2d1a219a3696b6e64a736408fc785cc_00000000_cab_19c8a127
               0 File(s)              0 bytes
              20 Dir(s)  23,291,658,240 bytes free

Inside the WER folder, the ReportsArchive folder contains reports that have already been uploaded to Watson. Data in these reports is deleted, but the Report.wer file persists.

PS C:Windowssystem32> dir C:ProgramDataMicrosoftWindowsWERReportArchive

Here’s an example of the output:

Volume in drive C is INSTALLTO
Volume Serial Number is 4031-E397

Directory of c:ProgramDataMicrosoftWindowsWERReportArchive

<date>  <time>    <DIR>          .
<date>  <time>    <DIR>          ..
<date>  <time>    <DIR>          Critical_powershell.exe_7dd54f49935ce48b2dd99d1c64df29a5cfb73db_00000000_cab_096cc802
               0 File(s)              0 bytes
               3 Dir(s)  23,291,658,240 bytes free

Windows Error Reporting provides many settings to customize the problem reporting experience. For further information, please refer to the Windows Error Reporting documentation.

Troubleshooting using Windows Error Reporting reports

Physical disk failed to come online

To diagnose this issue, navigate to the WER report folder:

PS C:Windowssystem32> dir C:ProgramDataMicrosoftWindowsWERReportArchiveCritical_PhysicalDisk_b46b8883d892cfa8a26263afca228b17df8133d_00000000_cab_08abc39c

Here’s an example of the output:

Volume in drive C is INSTALLTO
Volume Serial Number is 4031-E397

<date>  <time>    <DIR>          .
<date>  <time>    <DIR>          ..
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_1.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_10.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_11.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_12.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_13.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_14.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_15.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_16.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_17.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_18.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_19.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_2.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_20.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_21.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_22.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_23.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_24.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_25.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_26.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_27.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_28.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_29.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_3.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_30.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_31.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_32.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_33.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_34.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_35.evtx
<date>  <time>         2,166,784 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_36.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_37.evtx
<date>  <time>            33,194 Report.wer
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_38.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_39.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_4.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_40.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_41.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_5.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_6.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_7.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_8.evtx
<date>  <time>            69,632 CLUSWER_RHS_ERROR_8d06c544-47a4-4396-96ec-af644f45c70a_9.evtx
<date>  <time>             7,382 WERC263.tmp.WERInternalMetadata.xml
<date>  <time>            59,202 WERC36D.tmp.csv
<date>  <time>            13,340 WERC38D.tmp.txt

Next, start triaging from the Report.wer file — this will tell you what failed.

EventType=Failover_clustering_resource_error
<skip>
Sig[0].Name=ResourceType
Sig[0].Value=Physical Disk
Sig[1].Name=CallType
Sig[1].Value=ONLINERESOURCE
Sig[2].Name=RHSCallResult
Sig[2].Value=5018
Sig[3].Name=ApplicationCallResult
Sig[3].Value=999
Sig[4].Name=DumpPolicy
Sig[4].Value=5225058577
DynamicSig[1].Name=OS Version
DynamicSig[1].Value=10.0.17051.2.0.0.400.8
DynamicSig[2].Name=Locale ID
DynamicSig[2].Value=1033
DynamicSig[27].Name=ResourceName
DynamicSig[27].Value=Cluster Disk 10
DynamicSig[28].Name=ReportId
DynamicSig[28].Value=8d06c544-47a4-4396-96ec-af644f45c70a
DynamicSig[29].Name=FailureTime
DynamicSig[29].Value=2017//12//12-22:38:05.485

Since the resource failed to come online, no dumps were collected, but the Windows Error Reporting report did collect logs. If you open all .evtx files using Microsoft Message Analyzer, you will see all of the information that was collected using the following queries through the system channel, application channel, failover cluster diagnostic channels, and a few other generic channels.

PS C:Windowssystem32> (Get-ClusterResourceType -Name "Physical Disk").DumpLogQuery

Here’s an example of the output:

<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Kernel-PnP/Configuration">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-ReFS/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Ntfs/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Ntfs/WHC">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Storport/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Storport/Health">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Storport/Admin">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-ClassPnP/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-ClassPnP/Admin">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-ScmBus/Certification">*[System[TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-ScmBus/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-PmemDisk/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-NvdimmN/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-INvdimm/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-PersistentMemory-VirtualNvdimm/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Disk/Admin">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Disk/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-ScmDisk0101/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Partition/Diagnostic">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Volume/Diagnostic">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-VolumeSnapshot-Driver/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-FailoverClustering-Clusport/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-FailoverClustering-ClusBflt/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-StorageSpaces-Driver/Diagnostic">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-StorageManagement/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-StorageSpaces-Driver/Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Storage-Tiering/Admin">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Hyper-V-VmSwitch-Operational">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>
<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Hyper-V-VmSwitch-Diagnostic">*[System[TimeCreated[timediff(@SystemTime) &lt;= 600000]]]</Select></Query></QueryList>

Message Analyzer enables you to capture, display, and analyze protocol messaging traffic. It also lets you trace and assess system events and other messages from Windows components. You can download Microsoft Message Analyzer from here. When you load the logs into Message Analyzer, you will see the following providers and messages from the log channels.

Loading logs into Message Analyzer

You can also group by providers to get the following view:

Logs grouped by providers

To identify why the disk failed, navigate to the events under FailoverClustering/Diagnostic and FailoverClustering/DiagnosticVerbose. Then run the following query: EventLog.EventData[«LogString»] contains «Cluster Disk 10». This will give you give you the following output:

Output of running log query

Physical disk timed out

To diagnose this issue, navigate to the WER report folder. The folder contains log files and dump files for RHS, clussvc.exe, and of the process that hosts the «smphost» service, as shown below:

PS C:Windowssystem32> dir C:ProgramDataMicrosoftWindowsWERReportArchiveCritical_PhysicalDisk_64acaf7e4590828ae8a3ac3c8b31da9a789586d4_00000000_cab_1d94712e

Here’s an example of the output:

Volume in drive C is INSTALLTO
Volume Serial Number is 4031-E397

<date>  <time>    <DIR>          .
<date>  <time>    <DIR>          ..
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_1.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_10.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_11.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_12.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_13.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_14.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_15.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_16.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_17.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_18.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_19.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_2.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_20.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_21.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_22.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_23.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_24.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_25.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_26.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_27.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_28.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_29.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_3.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_30.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_31.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_32.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_33.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_34.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_35.evtx
<date>  <time>         2,166,784 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_36.evtx
<date>  <time>         1,118,208 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_37.evtx
<date>  <time>        28,340,500 memory.hdmp
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_38.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_39.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_4.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_40.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_41.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_5.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_6.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_7.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_8.evtx
<date>  <time>            69,632 CLUSWER_RHS_HANG_75e60318-50c9-41e4-94d9-fb0f589cd224_9.evtx
<date>  <time>         4,466,943 minidump.0f14.mdmp
<date>  <time>         1,735,776 minidump.2200.mdmp
<date>  <time>            33,890 Report.wer
<date>  <time>            49,267 WER69FA.tmp.mdmp
<date>  <time>             5,706 WER70A2.tmp.WERInternalMetadata.xml
<date>  <time>            63,206 WER70E0.tmp.csv
<date>  <time>            13,340 WER7100.tmp.txt

Next, start triaging from the Report.wer file — this will tell you what call or resource is hanging.

EventType=Failover_clustering_resource_timeout_2
<skip>
Sig[0].Name=ResourceType
Sig[0].Value=Physical Disk
Sig[1].Name=CallType
Sig[1].Value=ONLINERESOURCE
Sig[2].Name=DumpPolicy
Sig[2].Value=5225058577
Sig[3].Name=ControlCode
Sig[3].Value=18
DynamicSig[1].Name=OS Version
DynamicSig[1].Value=10.0.17051.2.0.0.400.8
DynamicSig[2].Name=Locale ID
DynamicSig[2].Value=1033
DynamicSig[26].Name=ResourceName
DynamicSig[26].Value=Cluster Disk 10
DynamicSig[27].Name=ReportId
DynamicSig[27].Value=75e60318-50c9-41e4-94d9-fb0f589cd224
DynamicSig[29].Name=HangThreadId
DynamicSig[29].Value=10008

The list of services and processes that we collect in a dump is controlled by the following property: PS C:Windowssystem32> (Get-ClusterResourceType -Name «Physical Disk»).DumpServicesSmphost

To identify why the hang happened, open the dump files. Then run the following query: EventLog.EventData[«LogString»] contains «Cluster Disk 10» This will give you give you the following output:

Output of running log query 2

We can cross-examine this with the thread from the memory.hdmp file:

# 21  Id: 1d98.2718 Suspend: 0 Teb: 0000000b`f1f7b000 Unfrozen
# Child-SP          RetAddr           Call Site
00 0000000b`f3c7ec38 00007ff8`455d25ca ntdll!ZwDelayExecution+0x14
01 0000000b`f3c7ec40 00007ff8`2ef19710 KERNELBASE!SleepEx+0x9a
02 0000000b`f3c7ece0 00007ff8`3bdf7fbf clusres!ResHardDiskOnlineOrTurnOffMMThread+0x2b0
03 0000000b`f3c7f960 00007ff8`391eed34 resutils!ClusWorkerStart+0x5f
04 0000000b`f3c7f9d0 00000000`00000000 vfbasics+0xed34

Windows Error Reporting (сокращённо: WER) — это набор технологий, встроенных в Windows, который собирает информацию о сбое в приложениях при их вылетах (а также о сбоях ядра) и отправляет её на сервера Microsoft. Разработчик программного обеспечения по этой информации может разработать и опубликовать соответствующее обновление. Затем конечный пользователь, отправляя отчёт, увидит, что для этой ошибки в программе доступно исправление, сможет скачать его и обновить программу.

В этой статье я хотел бы посмотреть на его историю, концепцию и как вы можете использовать его на практике для своих приложений Delphi (или, наоборот, не использовать).

Содержание

  • Что происходит при сбое
  • История Windows Error Reporting
    • 16-битные Windows
    • 32-битные Windows
    • Windows XP
    • Windows Vista и позднее
  • Приложение скрытно завершается — что делать?
  • Что Delphi делает не так
  • Когда приложение Delphi может привести к вызову WER?
    • Необработанное исключение в не-RTL потоке
    • Повреждение стека/обработчика
    • Переполнение стека
    • Двойное переполнение стека
    • Вызов UnhandledExceptionFilter
    • Перенаправление на JIT-отладчик
    • Ручной вызов WER
  • Настройка Delphi-приложений для отправки отчётов
    • Зачем это делать
    • Лирическое отступление для тех, кому вообще не нравится концепция отправки отчётов
  • Настройка посмертного отладчика
    • Использование Threads Snapshot в качестве посмертного отладчика
  • Что я могу извлечь из отчёта WER?
    • Тип вылета
    • Код исключения
    • Класс исключения
    • Смещение/адрес исключения
    • Стек, переменные и другая информация
  • Как мне получать отчёты WER?
  • Что если я хочу использовать отчёты, но не хочу использовать WER?
    • Что если я не хочу изобретать при этом велосипед?
  • Дополнительные материалы для чтения

Что происходит при сбое

Сбой в приложении как правило представляет собой исключение, которое может быть или программным (вызываемым явно через raise, т.е. происходящим синхронно) или аппаратным (прерыванием от процессора, т.е. происходящим асинхронно — например, ошибочная попытка доступа к памяти).

Когда происходит исключение, система ищет обработчики в таком порядке:

  1. Если к программе подключен отладчик, система передаёт управление ему. Отладчик решает: остановить ли программу, продолжить ли её выполнение, или же показать уведомление.

  2. Если отладчик не подключен, система передаёт управление на блок обработки пользовательского кода (try/finally и try/except). Как правило, такие обработчики либо обрабатывают исключения сами, реализуя логику отката, либо показывают сообщение об ошибке пользователю.
    В частности, в Delphi даже если вы явно не написали в своём коде блок try/except — всё равно RTL Delphi содержит такой блок, который ловит все исключения и показывает сообщение об ошибке (см. также раздел «Что Delphi делает не так» ниже):

  3. Если пользовательского блока нет, либо они закончились (обработчики могут быть вложены и выполняются по очереди — это называется SEH, Structured Exception Handling, структурированная обработка исключений), система передаёт управление глобальному обработчику необработанных исключений (unhandled exception handler).
    Тут надо заметить, что в Delphi приложениях это бывает крайне редко (см. раздел «Что Delphi делает не так» ниже).
  4. Если глобального обработчика нет — программа «вылетает», т.е. система завершает процесс.

  5. Поскольку сообщение показывается из самого процесса, то в особо запущенных случаях процесс может быть завершён без показа сообщения.

(для простоты я опустил случай подключения отладчика ядра)

16-битные Windows

В далёкие-далёкие времена не было никаких утилит диагностики. Если программа вылетала — она вылетала совсем. В тяжёлых случаях программа могла утянуть с собой всю систему.

К примеру, General Protection Fault (GPF, общее нарушение защиты, Unrecoverable Application Error, UAE) — это прерывание (fault), возникающее в случае попытки приложения получить доступ к не принадлежащей ему области памяти (сегодня известно как исключение Access Violation большинству разработчиков Delphi). При получении этого сигнала от процессора операционная система останавливает выполнение приложения, сообщает пользователю и продолжает выполнение других приложений. Но если в процессе обработки GPF (в обработчике GPF) будет возбуждено ещё одно GPF, процессор отправит сигнал «повторный GPF» (double fault), останавливая уже операционную систему. Если при этом снова произойдёт GPF (triple fault), процессор прекратит работу и его нельзя будет перезапустить (нужен будет перезапуск всего компьютера).

В те времена основным способом исправить ошибку в программе было воспроизведение проблемы под отладчиком.

Первая программа диагностики появилась в бета-версии 16-битной Windows 3.0. Она была создана Доном Корбиттом (Don Corbitt), который раньше работал в Borland и был частью TeamB, но потом ушёл в Microsoft, где и написал Доктора Ватсона (Dr. Watson) — первую утилиту сбора информации о вылете приложения в Windows. Как вы, вероятно, уже предположили, имя «Доктор Ватсон» взято у Доктора Ватсона — персонажа историй Артура Конана Дойля про Шерлока Холмса.

Доктор Ватсон собирал информацию о системе, сбое и состоянии программы («симптомы»). Информация записывалась в отладочный лог-файл, который потом мог быть доставлен разработчикам программы для анализа.

Конечно же, Доктор Ватсон очень понравился разработчикам программ. Мэтт Питрек (автор «Windows Internals» и «Undocumented Windows», тоже, кстати, работал в то время в Borland и тоже входил в TeamB) написал свою собственную версию, изначально называвшуюся «Доктор Франк» («Dr. Frank») — в честь Франка Борленда, основателя Borland. Доктор Франк имел кучу дополнительных возможностей, которые делали его круче Доктора Ватсона. Borland-у понравилась идея утилиты и они включили Доктора Франка в состав Borland C++ 3.1 — к сожалению, переименовав его в WinSpector.

Компилятор Watcom C также стал поставляться со своим собственным аналогом Доктора Ватсона, называвшегося «Доктор Ватком» (Dr. Watcom).

Пример необработанного исключения в Delphi 1, которое было поймано WinSpector:

См. также:

  • Как изначально назывался Доктор Ватсон?
  • Дополнение к посту Реймонда о Докторе Ватсоне

32-битные Windows

Вплоть до Windows 2000 Доктора Ватсона нужно было запускать вручную до запуска программы, в которой происходил вылет. Доктор Ватсон просто работал в фоне и мог собирать информацию о системе, которая потом сбрасывалась в текстовый отчёт.

В Windows 2000 был предусмотрен новый механизм. С помощью ключа реестра HKLMSOFTWAREMicrosoftWindows NTCurrentVersionAeDebug (HKLMSoftwareWow6432NodeMicrosoftWindows NTCurrentVersionAeDebug — для 32-битных программ в 64-битной системе) стало возможным указывать т.н. «посмертный отладчик» (postmortem debugger) или JIT-отладчик (Just-In-Time — «как раз вовремя») — для подключения к процессу при его вылете. Этот ключ реестра — документирован в MSDN и TechNet. Вы могли зарегистрировать Доктора Ватсона в качестве такого отладчика, вызвав:

drwtsn32.exe -i

В результате чего Доктор Ватсон регистрировал сам себя:

После этого при вылете приложения система читала ключ реестра, запускала Доктора Ватсона, он подключался к приостановленному процессу, собирал информацию:

При этом, если параметр Auto ключа реестра AeDebug был равен True (1), то зарегистрированный отладчик запускался сразу, иначе — система выводила обычное сообщение, но с одной дополнительной кнопкой: «Отмена» — для запуска отладчика. Да, вот так коряво была добавлена эта возможность в Windows. Никто не удосужился даже сделать подходящий диалог.

Примечание: строго говоря, ключ реестра AeDebug был ещё в WinNT, а в линейке 9x его функциональность выполнял похожий раздел в Win.ini, тем не менее, ключ -i у Доктора Ватсона впервые появился именно в Windows 2000.

В любом случае, эту информацию можно было затем просмотреть и отправить разработчику программы:

Это был прообраз того, что затем стало службой Windows Error Reporting.

P.S. Разумеется, если у вас был установлен полноценный отладчик, то никто не мешал вам зарегистрировать этот отладчик как посмертный, вместо Доктора Ватсона — что, собственно говоря, и делают Delphi (и Visual Studio). Более того, если в системе зарегистрирован посмертный отладчик, то любое приложение может форсированно его вызвать, сделав вызов системной функции DebugBreak, которая состоит всего из одной ассемблерной команды: $CC — программная (пользовательская) точка останова. Разумеется, если посмертный отладчик не зарегистрирован и программа не отлаживается, то такой код приведёт к обычному вылету приложения.

См. также:

  • Вызывая Доктора Ватсона
  • Почему Windows Error Reporting называется Доктором Ватсоном?

Windows XP

В Windows XP Доктор Ватсон был существенно расширен и вылизан. Кроме того, он сменил имя на «Problem Reports and Solutions» и представлен dwwin.exe (Microsoft Application Error Reporting) и FaultRep.dll.

Во-первых, в Windows XP Доктор Ватсон зарегистрирован по умолчанию в качестве посмертного отладчика, его не нужно регистрировать вручную (несмотря на то, что Доктор Ватсон зарегистрирован в AeDebug / Debugger как drwtsn32.exe, фактически drwtsn32.exe является переходником к dwwin.exe, который и выполняет всю работу).

Во-вторых, он может быть вызван из программы вручную — через функцию ReportFault.

В-третьих, он добавляет события о вылетах в системный журнал.

Наконец, в-четвёртых, он может быть сконфигурирован из апплета Система Панели Управления:

Как вы можете видеть, тут есть куча опций — выключить, включить частично, полностью, указать исключения или белый список. Достаточно широкий набор опций.

Если вы выключите отчёты полностью (сняв даже галочку с «уведомлять о критических ошибках»), то при вылете приложения система просто завершит процесс, не показав никакого сообщения и даже не сделав отметку в системном журнале. Это полезно, если система работает в основном без присутствия пользователя, либо когда появление дополнительных окон крайне не желательно (например, режим киоска).

Примечание: однако, если вместо этого вы вручную удалите параметр Debugger ключа AeDebug (в котором и зарегистрирован Доктор Ватсон), то настройки, конечно, будут игнорироваться. Система покажет обычное окно о фатальном сбое в приложении:

Если же вы выключите отчёты, но включите опцию «уведомлять о критических ошибках», то Доктор Ватсон будет показывать отчёты о вылетах приложений:

но не будет показывать отчёты, сгенерированные вручную (через ReportFault). События о вылетах также будут добавлены в системный лог:

Включение же отчётов покажет в диалоге новую опцию: «Отправить отчёт».

при нажатии на которую собранный отчёт отправляется на серверы Microsoft:

а также добавит отдельное событие в системный лог:

Но почему отчёт отправляется Microsoft, а не разработчику программы? Дело в том, что вылет в модуле (exe или DLL) может не быть виной этого модуля. Быть может просто другой модуль неверно нас вызвал. Например, вылет проводника может быть из-за кривого расширения оболочки. Вылет игры может быть обусловлен глюком в видеодрайвере и т.д. Вот почему отчёты отправляются в централизованное хранилище. Там они сортируются и к отчётам допускаются все разработчики, чьи модули (exe или DLL) были упомянуты в отчёте.

Microsoft не использует данные отчётов для каких-либо маркетинговых анализов, анализов частоты ошибок в различных программах т.п. Все данные пользователей защищаются от постороннего доступа и используются только для поиска причины ошибки. Отчёты отправляются по защищённому SSL соединению и хранятся на защищённых серверах, которые не используются ни для каких других целей. Для доступа к отчётам нужно предоставить логин и пароль (задаваемые при регистрации в WinQual). Любой разработчик может видеть только отчёты для своих программ. Все компании, использующие WER, обязуются следовать аналогичным политикам. Разумеется, нет гарантий, что небольшая компания из трёх человек, занимающаяся разработкой shareware-софта, заинтересована в соблюдении вашей конфиденциальности столько, сколько сама Microsoft (хотя она и согласилась следовать соответствующим политикам). Кроме того, по-умолчанию данные отчёта не содержат никаких данных пользователя, кроме тех, которые случайно попадут в дамп. Т.е. персональные данные специально не собираются. В отчёт они могут попасть только случайно. Тем не менее, вы можете посмотреть данные отчёта перед отправкой в случае, если вы работали с важными данными перед вылетом программы.

А зачем вообще нужно отправлять отчёты о вылетах приложения? Дело в том, что в противном случае разработчик увидит только небольшую часть проблем в своём приложении, про большинство проблем он не будет знать (ведь про них не сообщают). Если разработчик не знает про проблему — он её и не исправит.

Кроме того, автоматическая система сбора отчётов может сообщить вам, какая ошибка встречается в вашей программе чаще всего. Иными словами, вы можете сразу же исправить ту ошибку, которая действительно важная, которая мешает жить наибольшему количеству ваших пользователей. Т.е. вы можете исправить одну ошибку вместо сотни других — но эффект будет тот же самый.

В частности, после того как Microsoft реализовала механизм отправки отчётов в Windows XP, она позднее провела широкомасштабный анализ присланных данных, который показал, что 80% пользовательских проблем могут быть решены исправлением 20% наиболее «популярных» ошибок. Даже исправление 1% самых частых ошибок устранит 50% пользовательских проблем!

См. также:

  • EDD — Exception Driven Development, Разработка управляемая исключениями

Windows Vista и позднее

Неудивительно, что в Windows Vista служба отправки отчётов из Windows XP была снова обновлена. Она получила новое название Windows Error Reporting (WER) и представлена wermgr.exe и WER.dll.

Для начала, теперь WER вообще не нужно регистрироваться в системе. По умолчанию ключ AeDebug / Debugger вообще не существует, WER вызывается по умолчанию. Кроме того, в системе теперь есть специальная служба WerSvc («Windows Error Reporting Service» / «Служба регистрации ошибок Windows»), которая по умолчанию стоит на ручном (Manual) запуске и автоматически запускается системой при необходимости.

Бывший Доктор Ватсон теперь поддерживает настройку через групповые политики («Административные шаблоны» / «Компоненты Windows» / «Windows Error Reporting»), включая возможность указания альтернативных серверов для сбора отчётов (корпоративные настройки) и локальных дампов.

API был расширен и теперь приложения могут вручную сообщать не только о вылетах, но и о почти любой feedback-информации, прикреплять к отчёту дополнительные данные и файлы.

Отчёты теперь включают в себя не только логи, но и минидампы — слепки памяти процесса, которые могут быть загружены в отладчик на машине разработчика для исследования вылета.

Самое главное нововведение — появление локального центра отслеживания проблем. Он называется «Отчёты о проблемах и их решения», находится в Панели Управления:

В Windows 7 он был сгруппирован с «Обслуживанием» центра решений Windows («Центр безопасности и обслуживания»):

В Windows Vista можно было изменить все те же опции, что и в Windows XP:

Но уже в Windows 7 набор опций был уменьшен:

А в дальнейшем — и вовсе исчез (к примеру, в Windows 10 вообще нет настроек).

Сами настройки (из реестра) никуда не делись, их всё ещё можно поменять. Просто был убран UI — хотя всегда есть вариант использовать групповые политики.

В реестре эти настройки находятся здесь:

  • HKCUSoftwareMicrosoftWindowsWindows Error Reporting
  • HKLMSoftwareMicrosoftWindowsWindows Error Reporting
    HKLMSoftwareWOW6432NodeMicrosoftWindowsWindows Error Reporting

и официально документированы тут.

Как я уже сказал, самое значительное изменение — появление локального центра сбора отчётов. Отчёты/дампы теперь хранятся локально в %ALLUSERSPROFILE%MicrosoftWindowsWER (например: C:ProgramDataMicrosoftWindowsWER; а также они могут быть в %LOCALAPPDATA%CrashDumps) и могут быть отправлены в любой момент времени. Их можно просмотреть в так называемом «журнале стабильности работы»:

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

отправить любой отчёт или все сразу, а также удалить все отчёты. Опция «Проверить решения» заново отправит сигнатуры всех отчётов (что может быть очень долго!) и сообщит вам, если разработчики программ выпустили какие-либо исправления к этим отчётам.

Замечу, что «журнал стабильности работы» — это отдельный, независимый журнал. Он никак не связан с системным логом «Приложения» (Event Log). События о вылетах добавляются и туда, и туда, но очищать журналы можно индивидуально.

Поскольку WER теперь нельзя отключить удалением ключа AeDebug / Debugger, то записи в системном логе и журнале стабильности работы будут добавляться всегда — даже если служба WerSvc будет отключена (Disabled).

Вариант «Никогда не проверять решения» (Windows Error Reporting / Disabled = 1), как и ожидается, покажет простое сообщение об ошибке — с единственной опцией: закрыть программу.

Следующий вариант «Всегда спрашивать» (Windows Error Reporting / Disabled = 0; Windows Error Reporting / Concent / DefaultConsent = 1) предложит или закрыть программу или отправить отчёт:

Нажатие на кнопку отправки запустит отправку отчёта:

Тут надо заметить, что, на самом деле, в этот момент идёт не отправка непосредственно отчёта, а только проверка, нужно ли его отправлять. Т.е. на сервер отправляются идентификационные данные — т.н. первый уровень (Level One), состоящий из основных параметров, идентифицирующих вылет (имя приложения, модуля, их версии, адрес/смещение, тип вылета, время и т.п.). Если сервер сообщит, что эту проблему ещё никто не встречал, либо если разработчик захотел собрать дополнительные отчёты, то вам будет предложено отправить сам отчёт (это называется «отправка дополнительной информации») — т.н. второй уровень (Level Two), состоящий из детализированного отчёта, включая слепки памяти (минидампы процесса):

Заметьте, что в отличие от Windows XP, в Windows Vista помимо собственно лога прикладывается ещё и дамп процесса (.mdmp), который можно потом загрузить в отладчик Visual Studio или WinDbg, чтобы исследовать проблему.

В противном случае (о сбое уже сообщили, уровень два не отправляется) программа просто завершается (возможно, с финальным диалогом — см. ниже).

Соответственно, при выборе любой из двух «автоматических» опций (DefaultConsent = 2 для «авто-проверять» / «Parameters only»; и = 3 для «и отправлять» / «Parameters and safe data»), начальный вопрос «отправлять» / «закрывать» пропускается, а сразу идёт отправка. Разница между 2 и 3 состоит в том, что при 2 Windows автоматически отправляет только данные «уровня один» — параметры для идентификации отчёта. Все данные «уровня два» (т.е. сам отчёт) при этом отправляются только после явного разрешения пользователя. Вариант 3 же автоматически отправляет и «уровень один» и «уровень два», без запроса разрешения пользователя — но только данные, которые система смогла опознать как, вероятно, не содержащие личную информацию. Для отправки любой другой дополнительной информации Windows всё же спросит разрешения пользователя. На практике эти два положения обычно не отличаются.

Замечу, что DefaultConsent также может принимать значение 4 — автоматически отправлять вообще все данные без запроса пользователя. Это значение нельзя установить через UI в Windows Vista/7, хотя система его прекрасно понимает (т.е. диалог «отправка дополнительной информации» никогда не показывается). Но это значение стоит по умолчанию в последних версиях Windows (например, Windows 10).

Отключение службы WerSvc может повлиять на поведение отчётов. Например, Windows не сможет определить, был ли уже отправлен отчёт и каждый отчёт будет считать новым. Отправка отчётов внешне будет успешна, но, похоже, отчёты просто встают в очередь на отправку.

В некоторых случаях при вылете приложения Windows также может предложить поменять опции на «авто»:

Важно: если вы разрабатываете невизуальное приложение (или если ваше приложение вылетает до показа первого окна), то в случае, если вы установите опцию WER в любую из двух (трёх) «авто» позиций, диалог не будет показан, если в системе уже есть точно такой же вылет и отчёт по нему был отправлен! Иными словами, если (невидимое) приложение вылетело в первый раз — система сделает локальный отчёт и отправит его (автоматически). Диалог будет показан. Если же то же самое приложение вылетает ровно на этом же месте с той же проблемой второй раз — система просто втихую закроет (невидимое) приложение без показа сообщения! Очистка старых отчётов приведёт к тому, что вылет снова будет считаться «новым» и приведёт к показу сообщения об ошибке. Визуальные (оконные) приложения показывают диалог всегда. Вы также можете форсировать диалог для невизуальных приложений сделав вызов WerSetFlags(WER_FAULT_REPORTING_ALWAYS_SHOW_UI); при старте своего приложения.

(И наоборот, если вы сделаете вызов SetErrorMode(SEM_NOGPFAULTERRORBOX); или (недокументированный) WerSetFlags(WER_FAULT_REPORTING_NO_UI);, то ваше приложение не будет показывать диалог WER, даже если оно визуальное.)

В любом случае, в конечном итоге — если финальный диалог о вылете должен быть показан, то он выглядит так:

Если при этом вы зарегистрировали посмертный отладчик через AeDebug / Debugger, то будет показана дополнительная кнопка отладки:

Это последний диалог, после которого приложение будет закрыто (за исключением случаев, когда приложение самостоятельно инициировало обращение к WER — в этом случае дальнейшие действия определяет приложение).

См. также:

  • Как компания может получить доступ к данным Windows Error Reporting?

Приложение скрытно завершается — что делать?

Как должно быть уже понятно, это возможно в следующих случаях:

  • Приложение само явно вызвало TerminateProcess или ExitProcess (прямо или опосредованно — например, через Halt).
  • Приложение явно завершило все свои потоки (вышло из процедур потоков или же явно вызвало TerminateThread или ExitThread). Это не бывает в Delphi, поскольку компилятор Delphi вставляет неявный вызов Halt в конец главного потока (т.е. всегда вызывает ExitProcess в конце работы главного потока), но это может случиться, если внешний процесс уничтожит главный поток в вашей программе.
  • Какой-то внешний процесс закрыл или уничтожил или ваш процесс или все потоки в нём.
  • В вашей программе произошло необработанное (фатальное) исключение, но в Доктор Ватсон / WER отключен диалог об ошибках.
  • В вашей программе произошло необработанное (фатальное) исключение, в системе зарегистрирован сторонний посмертный отладчик с автоматическим запуском, который не показал сообщения об ошибке.
  • В вашей программе произошло необработанное (фатальное) исключение, которое настолько серьёзно, что система даже не смогла показать сообщение, а посмертный отладчик не зарегистрирован.

Что же в таком случае делать?

В целом нужно попытаться определить, завершилось ли приложение штатно (вызвало Exit/Terminate, либо это сделал кто-то за него), либо же вылетело с необработанным исключением. Подробный список — ниже.

Примечание: в списке ниже ключ реестра Windows Error Reportingчто-то обозначает ключ HKCUSoftwareMicrosoftWindowsWindows Error Reportingчто-то, а при его отсутствии — HKLMSoftwareMicrosoftWindowsWindows Error Reportingчто-то, либо HKLMSoftwareWow6432NodeMicrosoftWindowsWindows Error Reportingчто-то (для 32-битных приложений на 64-битной машине).

  1. Попробуйте запустить приложение под отладчиком. Убедитесь, что в отладчике не отключены уведомления об исключениях. Если приложение под отладчиком не вылетает или подключить отладчик нет возможности — см. шаги ниже.
  2. Первым делом удалите регистрацию посмертного отладчика в ключе реестра AeDebug, либо хотя бы сбросьте параметр Auto в 0.
  3. [Vista+] Убедитесь, что «Служба регистрации ошибок Windows» («Windows Error Reporting Service», WerSvc) не отключена (не находится в состоянии Disabled; по-умолчанию у неё тип запуска — Manual, но для надёжности вы можете её запустить вручную).
  4. Запустите Доктор Ватсон в Windows 2000, настройки отчётов в Windows XP, настройки WER в Windows Vista и позднее — и включите визуальные оповещения (Windows 2000), отчёты об ошибках (Windows XP), запрос согласия, т.е. не включайте автоматическую отправку (Windows Vista и выше).
  5. [Vista+] Проверьте настройки групповых политик WER. Убедитесь, что UI не отключен, логгинг не отключен, согласие (consent) не установлено в автоматическую отправку без запросов (DefaultConcent = 1). Не забудьте проверить как политики машины, так и пользователя.
  6. [Vista+] Убедитесь, что ключа реестра Windows Error ReportingDebugApplications* нет или он установлен в 1.
  7. [Vista+] Убедитесь, что ключа реестра Windows Error ReportingDontShowUI нет или он установлен в 0.
  8. [Vista+] Убедитесь, что ключа реестра Windows Error ReportingLoggingDisabled нет или он установлен в 0.
  9. [Vista+] Очистите все отчёты в журнале стабильности системы.
  10. Убедитесь, что вы не вызываете SetErrorMode с одним из следующих флагов: SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX. Для надёжности сделайте вызов SetErrorMode(0); первым действием при запуске своего приложения.
  11. [Win7+] Убедитесь, что вы не вызываете SetThreadErrorMode с одним из следующих флагов: SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX для ваших потоков. Для надёжности сделайте вызов SetThreadErrorMode(0); первым действием ваших потоков.
  12. [Vista+] Убедитесь, что ваш код не делает вызов WerSetFlags(WER_FAULT_REPORTING_NO_UI);.
  13. [Vista+] Сделайте вызов WerSetFlags(WER_FAULT_REPORTING_ALWAYS_SHOW_UI); первым действием при запуске своего приложения.
  14. Убедитесь, что глобальная переменная System.JITEnable равна 0. Для надёжности присвойте её 0 первым действием при старте приложения.
  15. Запустите ваше приложение и дайте ему вылететь. Если никакого диалога так и не появилось, то выполните нижеследующие шаги.
  16. Проверьте, есть ли записи о вылете приложения в системном логе «Приложения».
  17. Проверьте, нет ли свежих записей в «журнале стабильности системы» или логов в %APPDATA%MicrosoftWindowsWERReportArchive / %APPDATA%CrashDumps.
  18. Попробуйте назначить свой глобальный обработчик необработанных исключений через системную функцию SetUnhandledExceptionFilter.
  19. Установите точки останова или хуки (в рамках вашего процесса) на TerminateProcess, ExitProcess, а если это не помогло — то и на TerminateThread и ExitThread.
  20. Установите точки останова или хуки на системную функцию kernel32.KiUserExceptionDispatcher — если эта функция будет вызвана непосредственно перед вылетом, то 99% за то, что у вас произошло крайне серьёзное необработанное исключение, при котором система даже не смогла показать сообщение.
  21. Наконец, попробуйте установить глобальный хук (все процессы) на TerminateProcess, TerminateThread, чтобы узнать, не завершает ли ваш процесс кто-то ещё.
  22. Также попробуйте пересобрать приложение под x86-64 или использовать другую версию Delphi (как более новую, так и более старую).

Вы также можете использовать вспомогательные отладочные инструменты.

Что Delphi делает не так

Концепция обработки ошибок в Delphi была создана в Delphi 1 и существенно не менялась с тех пор. Напомню, что в те времена не было возможности подключения посмертного отладчика, сбора информации о сбое и автоматической отправки отчёта. Поэтому если приложение вылетало — оно вылетало с концами.

Поэтому Delphi исторически реализует концепцию «ни за что не вылетать». Делает это она путём расстановки блоков try/except везде, где только возможно. В частности, в оконных приложениях VCL обработка каждого сообщения заключена в явный блок try/except с вызовом Application.HandleException для обработки каждого необработанного (unhandled) исключения:

procedure TWinControl.MainWndProc(var Message: TMessage);
begin
  try
    try
      WindowProc(Message);
    finally
      FreeDeviceContexts;
      FreeMemoryContexts;
    end;
  except
    Application.HandleException(Self);
  end;
end;

Application.HandleException просто показывает сообщение (.Message) исключения (в новых версиях Delphi — вместе с вложенными):

После чего приложение продолжает обычную работу (цикл выборки сообщений).

Если VCL (или аналогичный фреймворк) не используется, либо если исключение происходит вне цикла выборки сообщений, то управление получает глобальный обработчик исключений RTL System._ExceptionHandler, который вызывает пользовательский обработчик из System.ExceptProc:

var
  ExceptProc: procedure(ExceptObject: TObject; ExceptAddr: Pointer); // Unhandled exception handler

В 99% случаев этот обработчик установлен в SysUtils.ExceptHandler, который показывает сообщение через SysUtils.ShowException, а затем завершает работу приложения с кодом возврата равным 1:

procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer);
begin
  ShowException(ExceptObject, ExceptAddr);
  Halt(1);
end;

SysUtils.ShowException показывает упрощённо-техническое сообщение:

Если же модуль SysUtils не подключен, либо исключение возникло до инициализации модуля SysUtils, то System.ExceptProc будет не назначен (равен nil), так что System._ExceptionHandler попытается обработать сообщение самостоятельно. Поскольку объект исключения определяется в том же модуле SysUtils, модуль System не может оперировать объектом исключения. Вместо этого он завершит приложение с «кодом ошибки» 217 (необработанное исключение) — через System._RunError:

procedure _RunError(errorCode: Byte);
begin
  ErrorAddr := ReturnAddress;
  Halt(errorCode);
end;

Что приведёт к завершению приложения. При этом System.Halt увидит, что установлен код ошибки, поэтому он сообщит об этом:

procedure _Halt0;
begin
  // ...

  if ErrorAddr <> nil then
  begin
    MakeErrorMessage;
    WriteErrorMessage;
    ErrorAddr := nil;
  end;

  // ...
  ExitProcess(ExitCode);
end;

Примечание: код 217, на самом деле, означает закрытие консольного приложения через Ctrl + Break. По совместительству он же используется для указания необработанного исключения. Также код может быть 230 — настоящий код для необработанных исключений, используется на не-Windows платформах. 204 — код для Invalid Pointer, вызывается менеджером памяти, если передать ему неверный указатель (например, указатель на уже удалённую память). А также частый код 216 — если необработанное исключения является аппаратным Access Violation. Для других аппаратных исключений также есть свои собственные коды, но на практике в 99% случаев вы увидите только 216, 217 или 204.

Если ваш код будет создавать дополнительные фоновые потоки через BeginThread (а равно и через любые обёртки к нему, например, TThread или многопоточный фреймворк), то RTL также оборачивает функцию потока в try/except блок с вызовом System._ExceptionHandler. А TThread и вовсе оборачивает .Execute в явный try/except блок с сохранением необработанного исключения в свойство .FatalException, таким образом полностью гася его обработку и оставляя её на ваше усмотрение.

Иными словами (почти) любой код Delphi оказывается обёрнут в обработчик RTL, все исключения обрабатываются либо вашим кодом, либо RTL, поэтому настоящих необработанных исключений в Delphi не бывает.

В те времена (Windows 3.x) это считалось несомненным плюсом — и таковым и преподносилось в рекламе Borland: «посмотрите, как надёжны наши приложения — они не вылетают». А если вылетают — показывают что-то более удобоваримое, чем просто GPF.

В современном мире повсеместного распространения интернета это оказывается уже не так здорово, как казалось когда то. Если ваше приложение не вылетает — оно не вызывает WER. Не вызывает WER — не отправляет отчёт. Не отправляет отчёт — вы не получаете отчёт. Результат? Вы или вообще не в курсе, что с вашим приложением что-то не так, либо получаете письмо от пользователя «программа не работает». Разве не было бы лучше немедленно узнавать о вылетах вашего приложения? Получать чёткие отчёты вида «проблема в строке 42»? Сортировать отчёты по частоте возникновения, чтобы видеть самые «горячие» проблемы?

Поэтому указанное поведение Delphi — зло, от которого нужно избавляться. Мы посмотрим на это чуть позже.

Когда приложение Delphi может привести к вызову WER?

А пока — посмотрим на то, как приложение Delphi может вызывать WER, даже хотя оно почти полностью завёрнуто в обработчики исключений, и необработанным исключениям возникнуть, вроде бы, неоткуда.

Необработанное исключение в не-RTL потоке

Для начала, самый простой случай — код в потоке, создаваемом системной функцией CreateThread, по очевидным причинам не будет иметь обработчика исключений RTL (в отличие от RTL-функции BeginThread).

function CrashMe(Arg: Pointer): Integer; stdcall;
begin
  raise Exception.Create('Apocalypse');
  Result := 0;
end;

...

TH := CreateThread(nil, 0, @CrashMe, nil, 0, TID);

или

function CrashMe(Arg: Pointer): Integer; stdcall;
var
  P: Pointer;
begin
  P := nil;
  P^ := 0; 
  Result := 0;
end;

...

TH := CreateThread(nil, 0, @CrashMe, nil, 0, TID);

Оба эти примера кода приведут к вылету приложения: будет вызван WER, создан/отправлен отчёт о вылете, приложение будет закрыто.

P.S. Кстати, этот метод — простейший способ заставить работать Restart & Recovery для приложений-служб. Просто реализуйте логику службы в потоках, создаваемых через CreateThread в OnStart. Не используйте OnExecute.

Повреждение стека/обработчика

Случай посложнее. Иногда система просто не может вызвать обработчики исключений: и тогда единственное, что она может сделать — вызвать глобальный обработчик (WER).

Например:

procedure TForm1.Button1Click(Sender: TObject);
var
  S: array [0..1] of Integer;
  I: Integer;
begin
  I := -6;          // пусть мы ошиблись в вычислении индекса I
  try
    S[I]     := 1;  // вместо записи в массив мы стираем обработчик исключений, установленный try
    S[I + 1] := 2;
    S[I + 2] := 3;
    Abort;          // <- полный вылет программы, т.к. система не смогла найти обработчик
  except
    ShowMessage('Aborted');
  end;
end;

Ошибки переполнения буфера могут привести к перезаписи важных адресов на стеке потока. В результате блоки try будут испорчены. И если в этот момент произойдёт исключение, система увидит на стеке мусор вместо указателя на блок except. Обработчик вызвать не удастся, так что будет вызван только WER.

Пример с перезаписью стека справедлив только для x86-32, т.к. на x86-64 стек не используется для блоков try (вместо этого используются табличные исключения, где все блоки try зарегистрированы в отдельной таблице, не хранящейся в стеке).

Переполнение стека

Тем не менее, можно привести и аналогичные примеры. Например:

procedure P;
begin
  P;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  P;
end;

Этот код вызывает просто переполнение стека (stack overflow). Часто — это обычное исключение, которое будет поймано ближайшим обработчиком и обработано. В данном случае — будет показано сообщение через Application.HandleException. Тем не менее, для обработки исключения тоже нужно свободное место на стеке. И если обработчик займёт слишком много места на стеке — получится исключение внутри обработчика исключения. Новое исключение будет передано выше, пока не дойдёт до самого верхнего уровня, где и будет обработано WER.

К примеру, сообщение о переполнении стека с высокой долей вероятности будет успешно показано из приложения Delphi 7 на Windows XP, поскольку обработчик представляет собой простой MessageBox(E.Message). Но в комбинации Delphi 10.1 Berlin на Windows 10 — приложение, вероятнее всего, вылетит в WER, поскольку там и обработчик немного сложнее и MessageBox устроен сложнее.

Двойное переполнение стека

Но даже если мы сведём обработчик к тривиальному:

procedure P;
begin
  P;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    P;
  except // <- тривиальный (пустой) обработчик
  end;
end;

который, очевидно, всегда будет выполняться успешно, т.к. не занимает места на стеке. Даже если мы возьмём наименьшую версию Delphi и Windows — всё равно приложение может вылететь.

Да, первое нажатие на кнопку возбудит исключение stack overflow, которое успешно будет обработано (пустым) обработчиком исключений. Но вспомните, что исключение stack overflow возбуждается только когда стек дорастает до защитной страницы, после доступа к которой защита снимается и возбуждается первое исключение (stack overflow). Если затем стек растёт и далее — то никакой защитной страницы уже нет, запись в стек наткнётся на зарезервированную страницу без доступа. Иными словами, если нажать кнопку второй раз — исключения stack overflow уже не будет. Будет фатальное Access Violation, будет вызван WER.

Вызов UnhandledExceptionFilter

Далее, поведение программы зависит и от версии Delphi. Указанная выше (в предыдущем разделе) логика с безусловным вызовом System.ExceptProc из System._ExceptionHandler справедлива лишь для старых версий Delphi. Относительно новые версии Delphi ведут себя так только при запуске под отладчиком. Если же программа запущена вне отладчика, то System._ExceptionHandler сначала вызовет системный UnhandledExceptionFilter — и вызовет System.ExceptProc только лишь если он вернул EXCEPTION_EXECUTE_HANDLER. Если же UnhandledExceptionFilter вернул EXCEPTION_CONTINUE_SEARCH, то System._ExceptionHandler не будет обрабатывать исключение и передаст его выше (т.е. исключение будет необработанным и его перехватит ОС, где в дело вступит WER). Если никто специально UnhandledExceptionFilter не назначал (Delphi его не назначает), то за его поведение отвечает WER, т.е. поведение зависит от ОС и настроек WER. К примеру, обработчик может ничего не делать и вернуть EXCEPTION_CONTINUE_SEARCH — и тогда исключение будет поднято выше, и вы увидите только диалог WER, но не run-time error. Часто обработчик сам обработает исключение (покажет диалог WER) и вернёт EXCEPTION_EXECUTE_HANDLER. И тогда вы увидите и диалог WER, и диалог run-time error. Воистину странное сочетание для пользователя!

Замечу, что это поведение (консультация с UnhandledExceptionFilter из System._ExceptionHandler) есть только под Windows, только при запуске вне отладчика, только при обработке исключения модулем System (т.е. не влияет на TThread и VCL), и почти, но не во всех версиях Delphi (правда, появилось оно очень давно).

Т.е. предполагается, что если вы запустили программу вне отладчика, она вылетела, то это поведение позволит вам подключить отладчик Delphi для диагностики проблемы (напомню, многие среды разработки, и Delphi в их числе, регистрируют свои отладчики в качестве посмертных через ключ реестра AeDebug). Именно поэтому эта опция ничего не делает, если вы уже отлаживаете свою программу: в конце концов, задача опции — подключить отладчик, а если вы уже отлаживаетесь, то подключать ничего не надо.

Перенаправление на JIT-отладчик

Далее, начиная с Delphi 5 в модуле System появляется новая глобальная переменная:

var
  JITEnable: Byte = 0;        { 1 to call UnhandledExceptionFilter if the exception is not a Pascal exception.
                                >1 to call UnhandledExceptionFilter for all exceptions }

по-умолчанию она равна 0 и означает, что все обработчики исключений работают как обычно. Если эта переменная отлична от нуля, то блоки except не будут вызываться если, во-первых, программа не отлаживается, во-вторых, исключение — аппаратное (для System.JITEnable = 1) или произвольное (System.JITEnable = 2). Переменная System.JITEnable не изменяется кодом RTL/VCL и предназначена для изменения вами.

Иными словами, эта настройка ничего не делает, если программа запущена под отладчиком. В этом случае программа будет работать как обычно, все блоки except будут выполняться, все обработчики будут запускаться. Но если программа запущена без отладчика, то эта опция позволяет выбрать как/чем обрабатывать исключения — встроенными обработчиками или отдавать исключения наружу. 1, соответственно, отдаёт наружу только аппаратные исключения (типа Access Violation), 2 — любые.

Ну, а когда вышла Windows XP (а затем — и Vista), ровно эта же переменная (System.JITEnable) позволила вызывать WER и инициировать, таким образом, отправку отчётов.

Т.е.

procedure TForm1.Button1Click(Sender: TObject);
var
  P: PInteger;
begin
  JITEnable := 1;
  P := nil;
  P^ := 0;    // <- вызовет WER (вне отладчика)
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  JITEnable := 1;
  raise Exception.Create('Error Message'); // <- запустит обработчик исключений VCL
                                           // Application.HandleException, что
                                           // покажет сообщений "Error Message"
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  JITEnable := 2;
  raise Exception.Create('Error Message'); // <- вызовет WER (вне отладчика)
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  P: PInteger;
begin
  JITEnable := 1;
  try
    P := nil;
    P^ := 0;    // <- вызовет WER (вне отладчика)
  except
    MessageBox(0, 'Error!', 'Caption', MB_OK); // <- не будет выполнен (вне отладчика)
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  P: PInteger;
begin
  JITEnable := 1;
  try
    P := AllocMem(1024);
    try
      P := nil;
      P^ := 0;    // <- вызовет WER (вне отладчика)
    finally
      FreeMem(P); // <- не будет выполнен (вне отладчика)
    end;
  except
    MessageBox(0, 'Error!', 'Caption', MB_OK); // <- не будет выполнен (вне отладчика)
  end;
end;

Очевидно, что переменная System.JITEnable не предназначена для использования в production-версии вашего кода. По крайней мере, при значении 2 — точно. Ведь в вашем коде написаны какие-то обработчики исключений, стоят блоки try/except, ваш код предполагает, что обработчики будут выполняться, ведь они выполняют какую-то работу по откату. Но если вы включаете опцию System.JITEnable, то ни один из ваших обработчиков не будет вызван.

System.JITEnable предназначена для отладки ваших приложений. Если в вашем приложении происходит исключение/вылет, которое вы не можете поймать под отладчиком (происходит только при запуске вне отладчика), то вы можете включить System.JITEnable и запустить вашу программу вне отладчика. Пусть она вылетит, вы подключите к программе посмертный отладчик (отладчик Delphi, конечно же) и исследуете проблему на месте.

Замечу, что даже если System.JITEnable отлична от нуля, то это просто передаст исключение «наверх», т.е. UnhandledExceptionFilter будет вызываться. И если вы (или кто-то ещё) назначит обработчик UnhandledExceptionFilter (через SetUnhandledExceptionFilter) и он будет возвращать EXCEPTION_EXECUTE_HANDLER (для всех или только избранных исключений), то соответствующие блоки except всё же будут выполняться. Таким образом, вы всегда можете сделать тонкую настройку, выполнять обработку только избранных исключений. И в таком виде System.JITEnable вполне имеет право на жизнь и в production-коде.

Ручной вызов WER

В конце концов, ваш код может просто вызвать WER вручную — через его API. Примеры вызова мы посмотрим ниже.

Настройка Delphi-приложений для отправки отчётов

Зачем это делать

Итак, как уже должно быть понятно, отправка отчётов при вылете приложения — есть хорошо, т.к. позволяет разработчикам программ узнавать о вылетах и проблемах в приложениях без участия пользователя, даёт точные технические описания и позволяет делать сортировку/статистику.

Кроме того, вызов системного обработчика ошибок (будь это WER или что-то ещё) — необходимое условие в некоторых отдельных случаях. Например, при сертификации своей программы для Windows, для реакции системных сервисов перезапуска (Restart & Recovery, в т.ч. в службах).

Поэтому с концепцией Delphi «ни за что не вылетать» нужно срочно что-то делать.

Лирическое отступление для тех, кому вообще не нравится концепция отправки отчётов

Если по каким-либо причинам вы не хотите отправлять отчёты и хотите всегда показывать своё сообщение пользователю:

  • Не используйте CreateThread. Всегда используйте BeginThread.
  • Не изменяйте System.JITEnable.
  • Установите:
    function ExecuteRTLHandlers(const Exception: TExceptionPointers): Integer; stdcall;
    const
      EXCEPTION_EXECUTE_HANDLER = 1;
    begin
      Result := EXCEPTION_EXECUTE_HANDLER;
    end;
    
    initialization
      SetUnhandledExceptionFilter(@ExecuteRTLHandlers);
    end.

    и/или:

    • Заключайте каждую функцию потока, созданного CreateThread/BeginThread/QueueUserWorkItem (и аналогичными функциями), в явный блок try/except.
    • Заключайте в блоки try/except каждую секцию initialization, каждую секцию finalization и блок begin/end .dpr проекта.

    В блоке except вы можете вызывать SysUtils.ShowException или свой собственный код.

  • Вызовите SetErrorMode(SEM_NOGPFAULTERRORBOX); при старте процесса (опционально можно добавить и другие флаги). См. также.
  • Обработайте терминальное исключение главного потока:
    uses
      SysUtils;
    
    // ...
    
    procedure CatchException;
    begin
      if ExceptObject = nil then
        Exit;
    
      ShowException(ExceptObject, ExceptAddr);
      TerminateProcess(GetCurrentProcess, 216);
    end;
    
    initialization
      // ...
    
    finalization
      CatchException;
    end.

    или:
    Заключайте в блоки try/except каждую секцию initialization, каждую секцию finalization и блок begin/end .dpr проекта. В блоке except вы можете вызывать SysUtils.ShowException или свой собственный код.

  • Опционально: замените все обработчики исключений на свои. Убедитесь, что вы дополнительно показываете в сообщении/диалоге: имя модуля, его версию и смещение исключения внутри этого модуля (см. ниже).

Примечание: а если вы принципиально не против идеи отчётов, но не хотите использовать WER, то — см. ниже.

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

Во-первых, в первом приближении очень хорошо сработала бы такая комбинация:

function WERPassThrough(const Exception: TExceptionPointers): Integer; stdcall;

  function IsUnhandledExceptionCode(const AExceptionCode: DWORD): Boolean;
  begin
    Result := (AExceptionCode = EXCEPTION_ACCESS_VIOLATION) or
              (AExceptionCode = EXCEPTION_PRIV_INSTRUCTION) or
              (AExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION) or
              (AExceptionCode = EXCEPTION_NONCONTINUABLE_EXCEPTION) or
              (AExceptionCode = EXCEPTION_DATATYPE_MISALIGNMENT) or
              (AExceptionCode = EXCEPTION_BREAKPOINT) or
              (AExceptionCode = EXCEPTION_SINGLE_STEP) or
              (AExceptionCode = EXCEPTION_INVALID_HANDLE);
  end;

const
  EXCEPTION_CONTINUE_SEARCH = 0;
  EXCEPTION_EXECUTE_HANDLER = 1;
begin
  if IsUnhandledExceptionCode(Exception.ExceptionRecord.ExceptionCode) then
    Result := EXCEPTION_CONTINUE_SEARCH
  else
    Result := EXCEPTION_EXECUTE_HANDLER;
end;

initialization
  JITEnable := 1;
  SetUnhandledExceptionFilter(@WERPassThrough);
end.

В данном случае мы вызываем WER для всех перечисленных кодов аппаратных исключений и RTL обработчики — для всего остального. Список, конечно, приведён как пример, и его нужно изменить под ваше приложение. Например, если вы делаете определение запуска виртуальной машины через выполнение секретной инструкции, то код EXCEPTION_PRIV_INSTRUCTION (и, возможно, EXCEPTION_ILLEGAL_INSTRUCTION) нужно убрать. Если вы проверяете аргументы выражений, то коды числовых исключений (деление на ноль, переполнение и т.п.) хорошо бы добавить. В тривиальном случае можно также считать что любое аппаратное исключение нужно передать WER. В сложном случае — фильтровать не только аппаратные, но и программные исключения (установив JITEnable в 2).

Но это решение — половинчатое. Ведь нам хотелось бы вызывать WER для всех необработанных исключений, вне зависимости от их типа, и не вызывать для всех остальных (опять же, вне зависимости от типа). Поэтому решение с JITEnable стоит отложить.

К сожалению, в Delphi не предусмотрено никакой возможности отменить только «глобальные» блоки try/except, нет никакого аналога опции JITEnable. Вместо этого нам придётся назначить свой обработчик и вызывать WER вручную — через API. Сама Microsoft такой подход считает допустимым, но не рекомендованным. Рекомендованный — конечно же, просто не ловить исключения, которые вы не знаете как обрабатывать.

Итак, для начала нам нужна функция, которую мы будем вызывать для необработанных исключений:

const
  cDelphiException = $0EEDFADE; // копия из SysUtils

type
  PtrUInt = {$IFDEF UNICODE}NativeUInt{$ELSE}Cardinal{$ENDIF};

var
  ReportFault: function(const pep: TExceptionPointers; dwOpt: DWORD): Cardinal; stdcall;

resourcestring
  // Копия из SysConst с заменой %s (класс исключения) на %8.8x (код)
  SException = 'Exception %8.8x in module %s at %p.' + sLineBreak + '%s%s' + sLineBreak;

procedure UnhandledExceptionHandler(EP: TExceptionPointers; const AErrorCode: Cardinal = 0); overload;

  // Копия ShowException из SysUtils с заменой ExceptObject: TObject на Code: Cardinal
  procedure ShowExceptionRecord(const ACode: Cardinal; const AAddr: Pointer);

    function ExceptionErrorMessage(ExceptCode: Cardinal; ExceptAddr: Pointer; Buffer: PChar; Size: Integer): Integer;

      function ConvertAddr(Address: Pointer): Pointer; assembler;
      asm
              TEST    EAX,EAX
              JE      @@1
              SUB     EAX, $1000
      @@1:
      end;

    var
      MsgPtr: PChar;
      MsgEnd: PChar;
      MsgLen: Integer;
      ModuleName: array[0..MAX_PATH] of Char;
      Temp: array[0..MAX_PATH] of Char;
      Format: array[0..255] of Char;
      Info: TMemoryBasicInformation;
      ConvertedAddress: Pointer;
    begin
      VirtualQuery(ExceptAddr, Info, sizeof(Info));
      if (Info.State <> MEM_COMMIT) or
        (GetModuleFilename(THandle(Info.AllocationBase), Temp, Length(Temp)) = 0) then
      begin
        GetModuleFileName(HInstance, Temp, Length(Temp));
        ConvertedAddress := ConvertAddr(ExceptAddr);
      end
      else
        PtrUInt(ConvertedAddress) := PtrUInt(ExceptAddr) - PtrUInt(Info.AllocationBase);
      StrLCopy(ModuleName, AnsiStrRScan(Temp, '') + 1, Length(ModuleName) - 1);
      MsgPtr := '';
      MsgEnd := '';
      if ExceptObject is Exception then
      begin
        MsgPtr := PChar(Exception(ExceptObject).Message);
        MsgLen := StrLen(MsgPtr);
        if (MsgLen <> 0) and (MsgPtr[MsgLen - 1] <> '.') then MsgEnd := '.';
      end;
      LoadString(FindResourceHInstance(HInstance), PResStringRec(@SException).Identifier, Format, Length(Format));
      StrLFmt(Buffer, Size, Format, [ExceptCode, ModuleName, ConvertedAddress, MsgPtr, MsgEnd]);
      Result := StrLen(Buffer);
    end;

  var
    Title: array[0..63] of Char;
    Buffer: array[0..1023] of Char;
    OemBuffer: array of AnsiChar;
    BufSize, OemBufSize: Integer;
    Dummy: Cardinal;
  begin
    BufSize := ExceptionErrorMessage(ACode, AAddr, Buffer, Length(Buffer));
    if IsConsole then
    begin
      Flush(Output);
      OemBufSize := WideCharToMultiByte(CP_OEMCP, 0, Buffer, BufSize, nil, 0, nil, nil);
      SetLength(OemBuffer, OemBufSize);
      WideCharToMultiByte(CP_OEMCP, 0, Buffer, BufSize, @OemBuffer[0], OemBufSize, nil, nil);
      WriteFile(GetStdHandle(STD_ERROR_HANDLE), OemBuffer[0], OemBufSize, Dummy, nil);
      WriteFile(GetStdHandle(STD_ERROR_HANDLE), sLineBreak, 2, Dummy, nil);
    end
    else
    begin
      LoadString(FindResourceHInstance(HInstance), PResStringRec(@SExceptTitle).Identifier, Title, Length(Title));
      MessageBox(0, Buffer, Title, MB_OK or MB_ICONSTOP or MB_TASKMODAL);
    end;
  end;

// Далее идёт уже наш код

const
  // Возможные коды ошибок для WER API:
  frrvOk             = 0;  // The function succeeded.
  frrvOkManifest     = 1;  // The function succeeded and the error reporting client was launched in manifest reporting mode.
  frrvOkQueued       = 2;  // The function succeeded and the fault report was queued for later reporting.
  frrvErr            = 3;  // The function failed but the error reporting client was launched.
  frrvErrNoDW        = 4;  // The error reporting client was unable to launch. The system will perform its default actions, such as displaying the standard exception dialog box and launching the debugger.
  frrvErrTimeout     = 5;  // The function timed out.
  frrvLaunchDebugger = 6;  // The function succeeded and the user launched the debugger.
  frrvOkHeadless     = 7;  // The function succeeded and the error reporting client was launched in silent reporting mode (no UI is used).
  frrvCancelled      = 15; // Не документирован

var
  WERResult: Cardinal;
  ErrorCode: Cardinal;
  EO: TObject;
  EA: Pointer;
  MBI: TMemoryBasicInformation;

begin
  // Сначала определим код, с которым завершится наше приложение
  if AErrorCode <> 0 then
    ErrorCode := AErrorCode
  else
    ErrorCode := EP.ExceptionRecord.ExceptionCode;

  // Для программных исключений адрес возбуждения = адресу Kernel32.RaiseException
  // Это довольно бесполезно для диагностики
  // Поэтому мы подменяем адрес возбуждения на "настоящий"
  // В случае Delphi исключений он сохранён в параметрах исключения
  if EP.ExceptionRecord.ExceptionCode = cDelphiException then
    EP.ExceptionRecord.ExceptionAddress := SysUtils.PExceptionRecord(EP.ExceptionRecord).ExceptAddr;

  // Вызываем WER
  if Assigned(ReportFault) then
  begin

    // В этом месте появится обычное окно WER "В программе - ошибка, идёт поиск решения"
    WERResult := ReportFault(EP, 0);

    case WERResult of
      frrvOk, frrvOkManifest, frrvOkQueued, frrvOkHeadless:
        // Успех
        TerminateProcess(GetCurrentProcess, ErrorCode);
      frrvLaunchDebugger:
      begin
        // Нужно запустить отладчик из ключа реестра AeDebug
        // Оставляю это в качестве домашнего задания,
        // чтобы не засорять пример
      end;
      frrvCancelled:
        TerminateProcess(GetCurrentProcess, ErrorCode);
    else begin end; // ошибки frrvErr*, в том числе - frrvErrNoDW, если WER отключен
    end;
  end;

  // Если WER нет или произошла ошибка - покажем обычное сообщение средствами SysUtils
  // Для этого нам нужен объект исключения на руках

  // Для Delphi исключений объект уже есть - извлекаем его
  EO := nil;
  if EP.ExceptionRecord.ExceptionCode = cDelphiException then
  begin
    EO := TObject(SysUtils.PExceptionRecord(EP.ExceptionRecord).ExceptObject);
    EA := SysUtils.PExceptionRecord(EP.ExceptionRecord).ExceptAddr;
  end;

  // На всякий случай проверим объект:
  // размещён ли он в доступной странице памяти.
  // Менеджер памяти Delphi может уже завершить работу к этому моменту.
  if (EO = nil) or
     (VirtualQuery(EO, MBI, SizeOf(MBI)) = 0) or
     (MBI.State <> MEM_COMMIT) then
    EO := nil;

  // Для аппаратных исключений объекта нет, либо объект был уже удалён - покажем своё сообщение
  if EO = nil then
    ShowExceptionRecord(EP.ExceptionRecord.ExceptionCode, EP.ExceptionRecord.ExceptionAddress)
  else
    ShowException(EO, EA);

  // Завершим приложение в любом случае - с выбранным кодом ошибки
  TerminateProcess(GetCurrentProcess, ErrorCode);
end;

procedure UnhandledExceptionHandler(const E: Exception); overload;
var
  EP: TExceptionPointers;
  ER: System.TExceptionRecord;
  ErrorCode: Cardinal;
begin
  // API требует ExceptionPointers - это пара ExceptionRecord и Context.

  // Для внешнего исключения ExceptionRecord уже есть
  if E is EExternal then
    EP.ExceptionRecord := Windows.PExceptionRecord(EExternal(E).ExceptionRecord)
  else
  // Для Delphi-исключения ExceptionRecord не хранится, мы его конструируем заново
  begin
    FillChar(ER, SizeOf(ER), 0);
    ER.ExceptionCode := cDelphiException;
    ER.ExceptionAddress := ExceptAddr;
    ER.NumberParameters := 2;
    ER.ExceptAddr := ExceptAddr;
    ER.ExceptObject := Pointer(E);

    EP.ExceptionRecord := Windows.PExceptionRecord(@ER);
  end;
  // К сожалению, контекст процессора взять негде
  EP.ContextRecord := nil;

  // Определяем код ошибки для завершения процесса
  // В основном этот кусок нужен ради EOSError.ErrorCode
  // При желании сюда же можно добавить EDatabaseError и EOleSysError/EOleException
  if E is EOSError then
    ErrorCode := EOSError(E).ErrorCode
  else
    ErrorCode := 0;

  UnhandledExceptionHandler(EP, ErrorCode);
end;

procedure InitWER;
var
  LibFaultrep: HMODULE;
begin
  // Получаем точку входа в API - доступна с WinXP
  LibFaultrep := LoadLibrary('faultrep.dll'); // Do Not Localize
  if LibFaultrep <> 0 then
    ReportFault := GetProcAddress(LibFaultrep, 'ReportFault'); // Do Not Localize
end;

Как видите, тут ужасно много всего.

Центральным местом тут является всего одна функция — ReportFault, которую и нужно нам вызвать. Проблема в том, что на вход функция просит пару ExceptionRecord и Context — это низкоуровневые понятия, которые недоступны из обычного блока except в Delphi. Поэтому большая часть кода посвящена преобразованию между этими двумя понятиями. К сожалению, весьма важный кусок информации — контекст процессора, не доступен. Чтобы его получить, нам придётся прибегнуть к нетривиальным ловушкам, рассмотрение которых выходит за рамки этой статьи.

Очень важно, что этот код должен работать без выделения памяти (в куче). Во-первых, нам не известно состояние менеджера памяти в момент вызова нашего обработчика. Может быть, обработчик был вызван как раз потому, что произошло исключение в менеджере памяти. И что тогда? Снова выделять память? Во-вторых, менеджер памяти может быть уже финализирован к моменту вызова вашего обработчика. Например, если исключение возникло в секции initialization модуля, то сначала сработает блок except процедуры InitUnits модуля System, который завершит модули (и менеджер памяти — в том числе), а затем уже вызовет UnhandledExceptionFilter (и, следовательно — наш обработчик). Выделение памяти у уже отработавшего менеджера памяти, опять же, ничем хорошим не закончится.

Именно по этой причине вызов GetProcAddress вынесен из кода UnhandledExceptionHandler — он выделяет память (см. реализацию GetProcAddress в модуле Windows).

Подсказка: если вы всё же хотите выделать память в своём обработчике — попробуйте установить альтернативный менеджер памяти. Для простоты это может быть переходник к HeapAlloc или VirtualAlloc.

(Это я ещё не про все подводные камни рассказал. Да, как видите, глобальная обработка исключений — не самое простое дело.)

Если вызов WER был неудачен (например, WER отключен), либо WER вообще отсутствует (Windows 2000 и ранее), то мы пытаемся показать сообщение как оно показывалось бы ранее — методом ShowException модуля SysUtils. Проблема опять же в том, что делать, если на руках нет объекта исключения Delphi, который можно было бы показать. Напомню, что в этом случае модуль System показывает run-time ошибку (обычно — 217, 216 или 204, как мы обсуждали это выше) — что, на мой взгляд, достаточно бесполезно. Поэтому вместо этого я предлагаю показывать код исключения — что и делает функция ShowExceptionRecord, которая является слегка видоизменённой ShowException.

Данный пример использует API уровня Windows XP (хотя будет работать в любой ОС), но вы также можете расширить этот пример на уровень Vista, чтобы полностью настроить поведение WER. В Vista вы можете изменять части диалога, добавлять в отчёт информацию и файлы и многое другое.

Итак, имея на руках функцию UnhandledExceptionHandler, мы можем назначить её в качестве глобального обработчика исключений:

type
  TDummyObject = class(TObject)
  private
    class procedure WERPassThrough(Sender: TObject; E: Exception);
  end;

class procedure TDummyObject.WERPassThrough(Sender: TObject; E: Exception);
begin
  UnhandledExceptionHandler(E);
end;

// Опционально:
function WERPassThrough(const E: TExceptionPointers): Integer; stdcall;
begin
  UnhandledExceptionHandler(E);
  Result := 0;
end;

// Опционально
procedure SetWERVisible;
const
  WER_FAULT_REPORTING_ALWAYS_SHOW_UI = 16;
var
  WerSetFlags: function(dwFlags: DWord): HRESULT; stdcall;
begin
  WerSetFlags := GetProcAddress(GetModuleHandle(kernel32), 'WerSetFlags');
  if Assigned(WerSetFlags) then
    WerSetFlags(WER_FAULT_REPORTING_ALWAYS_SHOW_UI);
end;

initialization
  InitWER;
  Application.OnException := TDummyObject.WERPassThrough;
  SetUnhandledExceptionFilter(@WERPassThrough);           // опционально
  SetWERVisible;                                          // опционально
end.

В данном примере UnhandledExceptionHandler устанавливается обработчиком для оконных приложений VCL, а для остальных мест — мы подразумеваем, что будет использован стандартный обработчик модуля System, который должен вызвать (в не самых древних Delphi) стандартный системный UnhandledExceptionFilter. Соответственно, мы назначаем свой обработчик, чтобы перехватить этот вызов и показать WER.

Конечно, в последнем случае гораздо проще просто ничего не делать, т.к., как мы помним из написанного выше, в не самых древних версиях Delphi при прогоне без отладчика исключение останется необработанным и будет поднято до WER. Именно поэтому этот код помечен «опциональным». Тем не менее, если вы захотите поменять WER на, скажем, свой собственный механизм обработки/логгирования исключений, то вам этот код будет нужен, так что я заодно его и показал.

Для совсем старых версий Delphi вы можете заменить (или дополнить) вызов SetUnhandledExceptionFilter на назначение обработчика ExceptProc.

Вызов же SetWERVisible нужен чтобы окно WER появлялось бы всегда, даже если ошибка произошла в невизуальной части нашего приложения (см. обсуждение этого поведения выше).

И если вы хотите получать отчёты, но не хотите использовать WER, то вы можете использовать полностью аналогичный код:

procedure UnhandledExceptionHandler(EP: TExceptionPointers; const AErrorCode: Cardinal = 0); overload;
begin
  // ваш собственный код обработки/логгирования "необработанных" исключений.
end;

procedure UnhandledExceptionHandler(const E: Exception); overload;
var
  EP: TExceptionPointers;
  ER: System.TExceptionRecord;
  ErrorCode: Cardinal;
begin
  if E is EExternal then
    EP.ExceptionRecord := Windows.PExceptionRecord(EExternal(E).ExceptionRecord)
  else
  begin
    FillChar(ER, SizeOf(ER), 0);
    ER.ExceptionCode := cDelphiException;
    ER.ExceptionAddress := ExceptAddr;
    ER.NumberParameters := 2;
    ER.ExceptAddr := ExceptAddr;
    ER.ExceptObject := Pointer(E);

    EP.ExceptionRecord := Windows.PExceptionRecord(@ER);
  end;
  EP.ContextRecord := nil;

  if E is EOSError then
    ErrorCode := EOSError(E).ErrorCode
  else
    ErrorCode := 0;

  UnhandledExceptionHandler(EP, ErrorCode);
end;

type
  TDummyObject = class(TObject)
  private
    class procedure CustomHandle(Sender: TObject; E: Exception);
  end;

{ TDummyObject }

class procedure TDummyObject.CustomHandle(Sender: TObject; E: Exception);
begin
  UnhandledExceptionHandler(E);
end;

function CustomHandle(const E: TExceptionPointers): Integer; stdcall;
begin
  UnhandledExceptionHandler(E);
  Result := 0;
end;

// Опционально
procedure SetWERVisible;
const
  WER_FAULT_REPORTING_ALWAYS_SHOW_UI = 16;
var
  WerSetFlags: function(dwFlags: DWord): HRESULT; stdcall;
begin
  WerSetFlags := GetProcAddress(GetModuleHandle(kernel32), 'WerSetFlags');
  if Assigned(WerSetFlags) then
    WerSetFlags(WER_FAULT_REPORTING_ALWAYS_SHOW_UI);
end;

initialization
  Application.OnException := TDummyObject.CustomHandle;
  SetUnhandledExceptionFilter(@CustomHandle); 
  SetWERVisible;                                          // опционально
end.

В этом случае вызов SetUnhandledExceptionFilter становится обязательным, а не опциональным.

Примеры выше были приведены для оконного VCL-приложения, где есть глобальный объект Application, предоставляющий событие OnException. Если вы пишите приложение с использованием другого фреймворка — вы должны адаптировать этот код, изменив назначение глобального обработчика. Например, FireMonkey тоже предоставляет свой собственный глобальный объект Application, у которого также есть событие OnException. Некоторые другие фреймворки предоставляют аналогичное событие. Например, IntraWeb предоставляет событие gServerController.OnLogException. Некоторые фреймворки в качестве такого события используют глобальную переменную Classes.ApplicationHandleException (например, OTL — Omni Thread Library) — так что вы можете назначить свой обработчик туда. Некоторые фреймворки предоставляют процедуру регистрации обработчика вроде RegisterExceptionFilter. Например — OTL и IntraWeb (TIWServerControllerBase.RegisterExceptionCallback). Некоторые фреймворки (в основном — многопоточные) не выпускают исключение наружу, а сохраняют его в свойстве типа .FatalException — например, TThread и OTL.

Короче говоря, вам нужно изучить документацию фреймворка, найти там как фреймворк обрабатывает исключения. И если фреймворк ловит все необработанные исключения, то узнать как можно назначить свой глобальный обработчик, назначить его — и вызывать из него UnhandledExceptionHandler из примера выше.

К сожалению, не всегда можно назначить свой обработчик. К примеру, службы (сервисы Win32) в Delphi не предоставляют ни события, ни какого-либо иного способа зарегистрировать обработчик. В большинстве случаев для обработки исключений вызывается не виртуальный метод LogMessage. В TServiceApplication есть динамический метод DoHandleException, но заменить его нет никакой возможности, т.к. класс TServiceApplication создаётся безусловно, он не извлекается из какой-либо глобальной переменной, в которую мы могли бы записать свой класс. Есть и другие фреймворки, где не предусмотрена возможность указания своего обработчика исключений.

Во всех таких случаях нет чистого решения. Вы можете только установить ловушку на нужный код методом сплайсинга.

И раз уж вы собираетесь вылетать, то сильно неплохо было бы настроить своё приложение на автоматический перезапуск. Делается это либо вручную (на Windows XP и младше), либо вы можете использовать Restart and Recovery API на Windows Vista и выше. Не забудьте только передавать флаг WER_SUBMIT_HONOR_RESTART. Рассмотрение Restart and Recovery API выходит за рамки этой статьи.

Настройка посмертного отладчика

Как я уже описывал, у вас есть возможность зарегистрировать любую программу на ваш выбор в качестве посмертного (postmortem) или JIT (Just-In-Time) отладчика. Когда приложение вылетает, система добавит кнопку «Отладка» («Debug») в диалог фатального вылета. Нажатие на эту кнопку запустит указанный вами отладчик, передав ему идентификатор процесса, в котором произошёл вылет. Отладчик сможет подключиться к процессу и исследовать его. Отладчик может быть классическим интерактивным — вроде отладчика Delphi. Или же это может быть автоматизированная утилита, которая просто соберёт информацию — вроде Доктора Ватсона.

Посмертный отладчик регистрируется в ключе реестра HKLMSOFTWAREMicrosoftWindows NTCurrentVersionAeDebug (или HKLMSOFTWAREWow6432NodeMicrosoftWindows NTCurrentVersionAeDebug — для 32-битных приложений в 64-битной системе). Для регистрации отладчика вам нужно создать или изменить значение Debugger в ключе AeDebug (строкового типа). Строка должна содержать корректную командную строку. Иными словами, если имя/путь отладчика включает в себя пробелы, его нужно заключать в кавычки. Путь обязательно должен быть полным и абсолютным.

В командной строке нужно указать как минимум один параметр %ld — это шаблон, который будет заменён на PID процесса.

Опционально можно добавить ещё два параметра: второй %ld будет заменён на описатель (handle) события, который отладчик может взвести, чтобы возобновить выполнения процесса. При этом считается, что отладчик исправил проблему, и процесс может продолжить работу. Если его не указывать, либо отладчик его не взведёт, то система будет ждать завершения процесса отладчика, после чего возобновит обычную работу WER, т.е. считается, что проблема не исправлена. В большинстве случаев это не очень полезная возможность, которой на практике обычно не пользуются.

Наконец, можно добавить третий параметр %p, который будет заменён на адрес записи JIT_DEBUG_INFO в целевом процессе. Отладчик (или вы, вручную) может прочитать оттуда дополнительную информацию.

type 
  _JIT_DEBUG_INFO = record
    dwSize: DWORD;
    dwProcessorArchitecture: DWORD;
    dwThreadID: DWORD;
    dwReserved0: DWORD;
    lpExceptionAddress: UInt64;
    lpExceptionRecord: UInt64;
    lpContextRecord: UInt64;
  end;
  JIT_DEBUG_INFO = _JIT_DEBUG_INFO;
  TJITDebugInfo = JIT_DEBUG_INFO;
  PJITDebugInfo = ^JIT_DEBUG_INFO;

Кроме того, вы можете создать строковый параметр Auto и установить его в ‘0’ или ‘1’. Несложно сообразить, что при Auto = 1 диалоговое окно не показывается, посмертный отладчик запускается сразу. При Auto = 0 (умолчание), соответственно, появляется обычное окно с дополнительной кнопкой «Отладка» («Debug»).

Примечание: поскольку настройки посмертного отладчика хранятся в HKEY_LOCAL_MACHINE — вам потребуются права администратора. Поэтому все действия ниже нужно выполнять из программ, запущенных под администратором (и с элевацией прав).

Delphi при установке регистрирует себя в качестве посмертного отладчика. Но если потом устанавливается другая среда разработки, то она может перезаписать регистрацию посмертного отладчика. К сожалению, в Delphi нет никакого способа автоматически перерегистрировать посмертный отладчик, вам придётся сделать это вручную.

К примеру, для Delphi 5-7 установите Debugger в:

"C:путь-к-DelphiBinbordbg70.exe" -aeargs %ld %ld

только замените 70 (Delphi 7) на 50 (Delphi 5) или 60 (Delphi 6).

Для более новых версий Delphi используйте:

"C:путь-к-DelphiBinBDS.exe" /attach:%ld;%ld

В настоящее время параметр %p не принимает ни одна версия Delphi.

Указанный выше отладчик Delphi 5-7 (bordbg70.exe) является, по-сути, удалённым отладчиком (remote debugger) и поэтому зависит от соответствующей библиотеки bordbk (bordbk50.dll, bordbk60.dll, bordbk61.dll, bordbk70.dll), которую можно найти в папке C:Program FilesCommon FilesBorland SharedDebugger (да, даже на 64-битной системе используется C:Program Files, а не C:Program Files (x86)).

Если при запуске отладчика Delphi 5-7 вы получаете сообщение о невозможности загрузки библиотеки bordbk, то соответствующую библиотеку нужно зарегистрировать вызовом tregsvr (лежит в папке bin Delphi) или regsvr32 (два экземпляра лежат в папке C:WindowsSystem32 — под каждую разрядность; вызывать нужно, разумеется, 32-битный), например:

C:WindowsSysWOW64regsvr32.exe "C:Program FilesCommon FilesBorland SharedDebuggerbordbk70.dll"

Помимо Delphi посмертным отладчиком регистрируются и отладчики от Microsoft. Только в отличие от Delphi, многие из них имеют возможность автоматической перерегистрации. Например, отладчики WinDbg, CDB и NTSD могут быть зарегистрированы следующими командами:

windbg -I
cdb.exe -iae
ntsd.exe -iae

К сожалению, отладчик WinDbg при этом не регистрирует параметр для JIT_DEBUG_INFO, поэтому лучше всего отредактировать его регистрацию вручную:

"C:путь-к-windbgwindbg.exe" -p %ld -e %ld -c ".jdinfo 0x%p"

Отладчик Visual Studio не имеет возможности перерегистрации, его нужно регистрировать заново вручную. Для этого используется такая командная строка:

"C:WINDOWSsystem32vsjitdebugger.exe" -p %ld -e %ld

Утилита ProcDump от SysInternals также может быть зарегистрирована посмертным отладчиком с помощью такой команды:

procdump.exe -i

Утилита ProcDump создаёт дамп процесса. По умолчанию создаётся мини-дамп: списки процессов, потоков, модулей, описателей. Дамп может быть расширен указанием опций -ma или -mp. Подробнее — см. справку по параметрам ProcDump.

Если вы пользуетесь в основном Delphi, то вы регистрируете отладчик Delphi в качестве посмертного — и на этом всё. Если же вы используете несколько сред разработки, то, возможно, вы бы хотели переключаться между отладчиками. В этом случае вы можете написать свою утилиту-переходник и зарегистрировать её в качестве посмертного отладчика. Например:

"C:ProjectsProject1.exe" %ld %ld %p

При запуске вы можете показать диалоговое окно со списком отладчиков, которые вы используете в работе. При выборе отладчика — запустите его через CreateProcess, передав ему отформатированные параметры командной строки (хотя бы так: «Args := StringReplace(StringReplace(StringReplace(Args, ‘%ld’, ParamStr(1), []), ‘%ld’, ParamStr(2), []), ‘%p’, ParamStr(3), []);»). Не забудьте наследовать описатель события (второй параметр %ld) в целевой процесс. Дождитесь завершения процесса отладчика и выходите.

Например, отладчик Visual Studio позволяет выбрать отладчик так:

Этот список позволяет выбрать native-отладчик, управляемый (.NET) или Java-отладчик. Delphi там, само собой, нет. Это я просто пример привёл, как это визуально может выглядеть, если вы захотите сделать такую утилиту самостоятельно.

Использование Threads Snapshot в качестве посмертного отладчика

В качестве посмертного отладчика вы также можете использовать утилиту Threads Snapshot. Она входит в состав трейсера исключений EurekaLog. Если у вас нет EurekaLog, то вы можете скачать бесплатный Tools Pack.

Для установки Threads Snapshot в качестве посмертного отладчика достаточно запустить её с параметром «/install«:

Само собой, запускать нужно под администратором (и с элевацией прав).

Когда утилита Threads Snapshot зарегистрирована в качестве посмертного отладчика, и вылетает любое приложение — вы можете нажать кнопку Debug для снятия снимка процесса:

Нажатие на кнопку Debug запустит снятие снимка процесса:

В конце утилита Threads Snapshot подготовит отчёт и спросит у вас, куда его сохранять. Отчёт будет сохранён в обычный .el формат (отчёт EurekaLog), который можно просмотреть в любом текстовом редакторе или в бесплатной утилите EurekaLog Viewer:

Восстановить регистрацию предыдущего посмертного отладчика можно запуском Threads Snapshot с параметром «/uninstall«.

Что я могу извлечь из отчёта WER?

Во-первых, вы можете просмотреть отчёт или его часть локально. Используйте «центр обслуживания» / «журнал стабильности работы» в новых версиях Windows или системный лог — в старых.

(Примечание: «Неправильный путь приложения» — это кривой перевод «Faulting Application Path»: «Путь к сбойнувшему приложению».)

Альтернативно можно использовать сторонюю утилиту AppCrashView — она показывает существенно больше технических деталей.

Сохраняемая в отчёте информация зависит от версии Windows, настроек WER и способа вызова WER из приложения (вызвало ли WER приложение само, через API, или нет; и если через API — то какие параметры указало).

Тип вылета

В первую очередь смотрим на тип вылета. Он может быть:

  • APPCRASH — необработанное исключение, самое частое событие.
  • APPHANG — зависание.
  • BEX — срабатывание защиты переполнение буфера (не реализовано в Delphi) или DEP (может случаться и в Delphi).
  • Пользовательские типы событий (если WER вызывается вручную; только на Vista+).

Код исключения

Далее, смотрим код ошибки. Он же — код исключения. Самые частые коды: $C0000005 — это Access Violation, $0EEDFADE — исключение-объект Delphi. Могут быть и другие коды. Если вы видите неопознанный код ошибки — попробуйте использовать утилиту Error Lookup. Она входит в состав трейсера исключений EurekaLog. Если у вас нет EurekaLog, то вы можете скачать бесплатный Tools Pack.

Класс исключения

Хорошо, с аппаратными исключениями понятно — они отличаются кодом. Но как отличить одно исключение Delphi от другого? Ведь любое исключение, представленное объектом Delphi, в результате будет иметь один и тот же код $0EEDFADE? Ну, в ситуации если WER вызван системой или через API уровня WinXP (т.е. функцию ReportFault, как мы сделали это в примере выше) — никак. Вам придётся исследовать слепок (дамп) процесса. Если же вы вызываете WER вручную (аналогично нашему примеру выше) и используете API уровня Vista+ (WerCreateReport/WerSubmitReport), то вы можете скопировать имя класса и/или сообщение в один из десяти произвольных строковых параметров отчёта (модифицированный код из примера выше):

const
  cDelphiException = $0EEDFADE; // копия из SysUtils

{$A+}
{$Z4}

// API уровня Windows XP

const
  // Возможные коды ошибок
  frrvOk             = 0;  // The function succeeded.
  frrvOkManifest     = 1;  // The function succeeded and the error reporting client was launched in manifest reporting mode.
  frrvOkQueued       = 2;  // The function succeeded and the fault report was queued for later reporting.
  frrvErr            = 3;  // The function failed but the error reporting client was launched.
  frrvErrNoDW        = 4;  // The error reporting client was unable to launch. The system will perform its default actions, such as displaying the standard exception dialog box and launching the debugger.
  frrvErrTimeout     = 5;  // The function timed out.
  frrvLaunchDebugger = 6;  // The function succeeded and the user launched the debugger.
  frrvOkHeadless     = 7;  // The function succeeded and the error reporting client was launched in silent reporting mode (no UI is used).
  frrvCancelled      = 15; // Не документирован

var
  ReportFault: function(const pep: TExceptionPointers; dwOpt: DWORD): Cardinal; stdcall;

// API уровня Windows Vista

const
  APPCRASH_EVENT = 'APPCRASH'; // Тип события для вылета приложения с необработанным исключением // Do Not Localize

  WER_P0 = 0;
  WER_P1 = 1;
  WER_P2 = 2;
  WER_P3 = 3;
  WER_P4 = 4;
  WER_P5 = 5;
  WER_P6 = 6;
  WER_P7 = 7;
  WER_P8 = 8;
  WER_P9 = 9;

  WER_SUBMIT_HONOR_RECOVERY             = 1;       // show recovery option
  WER_SUBMIT_HONOR_RESTART              = 2;       // show application restart option
  WER_SUBMIT_QUEUE                      = 4;       // report directly to queue
  WER_SUBMIT_SHOW_DEBUG                 = 8;       // show the debug button
  WER_SUBMIT_ADD_REGISTERED_DATA        = 16;      // Add registered data to the WER report
  WER_SUBMIT_OUTOFPROCESS               = 32;      // Force the report to go out of process
  WER_SUBMIT_NO_CLOSE_UI                = 64;      // Do not show the close dialog for the critical report
  WER_SUBMIT_NO_QUEUE                   = 128;     // Do not queue the report
  WER_SUBMIT_NO_ARCHIVE                 = 256;     // Do not archive the report
  WER_SUBMIT_START_MINIMIZED            = 512;     // The initial reporting UI is minimized and will flash
  WER_SUBMIT_OUTOFPROCESS_ASYNC         = 1024;    // The initial reporting UI is minimized and will flash
  WER_SUBMIT_BYPASS_DATA_THROTTLING     = 2048;    // [Win8+] Bypass data throttling for the report.
  WER_SUBMIT_ARCHIVE_PARAMETERS_ONLY    = 4096;    // [Win8+] Archive only the parameters; the cab is discarded. This flag overrides the ConfigureArchive WER setting.
  WER_SUBMIT_REPORT_MACHINE_ID          = 8192;    // [Win8+] Always send the unique, 128-bit computer identifier with the report, regardless of the consent with which the report was submitted.

type
  TWERReportType = (
    WerReportNonCritical,
    WerReportCritical,
    WerReportApplicationCrash,
    WerReportApplicationHang,
    WerReportKernel,
    WerReportInvalid);

  TWERReportInformation = record
    dwSize: DWORD;
    hProcess: THandle;
    wzConsentKey: array[0..64 - 1] of WideChar;
    wzFriendlyEventName: array[0..128 - 1] of WideChar;
    wzApplicationName: array[0..128 - 1] of WideChar;
    wzApplicationPath: array[0..MAX_PATH - 1] of WideChar;
    wzDescription: array[0..512 - 1] of WideChar;
    hwndParent: HWND;
  end;
  PWERReportInformation = ^TWERReportInformation;

  TWERConsent = (
    WerConsentReserved,
    WerConsentNotAsked,
    WerConsentApproved,
    WerConsentDenied,
    WerConsentMax);

  TWERSubmitResult = (
    WerReportReserved,
    WerReportQueued,
    WerReportUploaded,
    WerReportDebug,
    WerReportFailed,
    WerDisabled,
    WerReportCancelled,
    WerDisabledQueue,
    WerReportAsync,
    WerCustomAction);
  PWERSubmitResult = ^TWERSubmitResult;

  TWERDumpType = (
    WerDumpTypeUnknown,
    WerDumpTypeMicroDump,
    WerDumpTypeMiniDump,
    WerDumpTypeHeapDump,
    WerDumpTypeMax);

  PExceptionPointers = ^TExceptionPointers;

  TWERExceptionInformation = record
    pExceptionPointers: PExceptionPointers;
    bClientPointers: BOOL;
  end;
  PWERExceptionInformation = ^TWERExceptionInformation;

  TWERDumpCustomOptions = record
    dwSize: DWORD;
    dwMask: DWORD;
    dwDumpFlags: DWORD;
    bOnlyThisThread: BOOL;
    dwExceptionThreadFlags: DWORD;
    dwOtherThreadFlags: DWORD;
    dwExceptionThreadExFlags: DWORD;
    dwOtherThreadExFlags: DWORD;
    dwPreferredModuleFlags: DWORD;
    dwOtherModuleFlags: DWORD;
    wzPreferredModuleList: array[0..256 - 1] of WideChar;
  end;
  PWERDumpCustomOptions = ^TWERDumpCustomOptions;

var
  WerReportCreate:       function(pwzEventType: PWideChar; repType: TWERReportType; pReportInformation: PWERReportInformation; out phReportHandle: THandle): HRESULT; stdcall;
  WerReportSetParameter: function(hReportHandle: THandle; dwparamID: DWORD; pwzName: PWideChar; pwzValue: PWideChar): HRESULT; stdcall;
  WerReportAddDump:      function(hReportHandle: THandle; hProcess: THandle; hThread: THandle; dumpType: TWERDumpType; pExceptionParam: PWERExceptionInformation; pDumpCustomOptions: PWERDumpCustomOptions; dwFlags: DWORD): HRESULT; stdcall;
  WerReportSubmit:       function(hReportHandle: THandle; Consent: TWERConsent; dwFlags: DWord; out SubmitResult: TWERSubmitResult): HRESULT; stdcall;
  WerReportCloseHandle:  function(hReportHandle: THandle): HRESULT; stdcall;

resourcestring
  // Копия из SysConst с заменой %s (класс исключения) на %8.8x (код)
  SException = 'Exception %8.8x in module %s at %p.' + sLineBreak + '%s%s' + sLineBreak;

procedure UnhandledExceptionHandler(EP: TExceptionPointers; const AErrorCode: Cardinal = 0); overload;

  // Копия ShowException из SysUtils с заменой ExceptObject: TObject на Code: Cardinal
  procedure ShowExceptionRecord(const ACode: Cardinal; const AAddr: Pointer);
  begin
    // ... (код приведён выше)
  end;

  // Переводит буфер в нижний регистр (только ASCII)
  // Используется ниже при форматировании в HEX через StrLFmt
  procedure BufferToLowerCase(P: PWideChar; Len: Integer);
  var
    X: Integer;
  begin
    for X := 0 to Len - 1 do
    begin
      if (Ord('A') <= Word(P^)) and
         (Word(P^) <= Ord('Z')) then
        P^ := Char(Word(P^) or $0020);
      Inc(P);
    end;
  end;

  // Функция по сути повторяет стандартную работу WER по сбору параметров отчёта
  // Это т.н. первый уровень информации (Level One).
  procedure WERSetModuleParameters(const AWERReport: THandle; const AModule: HMODULE; const P1, P2, P3: Cardinal; const AName1, AName2, AName3: PWideChar);
  type
    TValue = array[0..50] of WideChar;

    procedure GetModuleVersion(const AFileName: PWideChar; out AVersion: TValue);
    const
      DefVersion: PWideChar = '0.0.0.0';
    var
      InfoSize: DWORD;
      Handle: DWORD;
      VerBuf: Pointer;
      FI: PVSFixedFileInfo;
      VerSize: DWORD;
      Major: Word;
      Minor: Word;
      Release: Word;
      Build: Word;
    begin
      FillChar(AVersion, SizeOf(AVersion), 0);
      Move(DefVersion^, AVersion, (Length('0.0.0.0') + 1) * SizeOf(WideChar));

      InfoSize := GetFileVersionInfoSizeW(PWideChar(AFileName), Handle);
      if InfoSize <> 0 then
      begin
        // Размер не известен и иногда может быть большим.
        // Мы не можем использовать стек, придётся использовать кучу.
        // Поскольку менеджер памяти может быть не рабочим, используем альтернативу.
        // Я выбрал HeapAlloc, но можно использовать и другие функции.
        VerBuf := HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, InfoSize);
        try
          if GetFileVersionInfoW(PWideChar(AFileName), Handle, InfoSize, VerBuf) then
            if VerQueryValue(VerBuf, '', Pointer(FI), VerSize) then
            begin
              Major := Hiword(FI.dwFileVersionMS);
              Minor := Loword(FI.dwFileVersionMS);
              Release := Hiword(FI.dwFileVersionLS);
              Build := Loword(FI.dwFileVersionLS);

              StrLFmt(@AVersion, Length(AVersion) - 1, '%d.%d.%d.%d', [Major, Minor, Release, Build]);
            end;
        finally
          HeapFree(GetProcessHeap, 0, VerBuf);
        end;
      end;
    end;

    function ReadResourceTime(const AImage: THandle): Cardinal;
    type
      _IMAGE_RESOURCE_DIRECTORY = record
        Characteristics: DWORD;
        TimeDateStamp: DWORD;
        // остальные поля нам не интересны, опускаем
      end;
      PImageResourceDirectory = ^_IMAGE_RESOURCE_DIRECTORY;
      PImageOptionalHeader = ^TImageOptionalHeader;

    var
      Header: PImageDosHeader;
      ImageNtHeaders: PImageNtHeaders;
      OptionalHeader: PImageOptionalHeader;
      DataDir: PImageDataDirectory;
      DOSTimeStamp: Cardinal;
      DT: TDateTime;
    begin
      Result := 0;

      Header := PImageDosHeader(AImage);
      if (Header.e_magic <> IMAGE_DOS_SIGNATURE) or
         (Header._lfanew = 0) then
        Exit;
      ImageNtHeaders := PImageNtHeaders(PAnsiChar(AImage) + Header._lfanew);
      if ImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE then
        Exit;

      // FileHeader.TimeDateStamp не заполняется некоторыми версиями компилятора Delphi,
      // поэтому мы используем время ресурсов
      OptionalHeader := @ImageNtHeaders.OptionalHeader;
      if OptionalHeader.NumberOfRvaAndSizes >= IMAGE_DIRECTORY_ENTRY_RESOURCE then
      begin
        DataDir := @OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
        if (DataDir.Size > 0) and
           (DataDir.VirtualAddress > 0) then
        begin
          DOSTimeStamp := PImageResourceDirectory(PAnsiChar(AImage) + DataDir.VirtualAddress)^.TimeDateStamp;
          DT := FileDateToDateTime(DOSTimeStamp);
          Result := DateTimeToUnix(DT);
        end;
      end;
    end;

  var
    FileName: array[0..MAX_PATH] of WideChar;
    Name: array[0..MAX_PATH] of WideChar;
    Version: TValue;
    X: Integer;
    LastPos: Integer;
    EndPos: Integer;
    CompilationTimeStamp: Cardinal;
  begin
    // Имя файла
    FillChar(FileName, SizeOf(FileName), 0);
    FillChar(Name, SizeOf(Name), 0);
    GetModuleFileNameW(AModule, @FileName, Length(FileName));

    LastPos := 0;
    EndPos := MAX_PATH + 1;
    for X := 0 to High(FileName) do
    begin
      if FileName[X] = #0 then
      begin
        EndPos := X;
        Break;
      end
      else
      if Word(FileName[X]) = Ord('') then
        LastPos := X;
    end;
    if LastPos > 0 then
      Move(FileName[LastPos + 1], Name[0], (EndPos - LastPos) * SizeOf(WideChar))
    else
      Move(FileName, Name, SizeOf(Name));
    WerReportSetParameter(AWERReport, P1, AName1, @Name);

    // Версия
    GetModuleVersion(FileName, Version);
    WerReportSetParameter(AWERReport, P2, AName2, @Version);

    // Метка времени
    // Стандартный WER добавляет её в виде DOS-ского timestamp
    CompilationTimeStamp := ReadResourceTime(AModule);
    FillChar(Name, SizeOf(Name), 0);
    StrLFmt(@Name, Length(Name) - 1, '%x', [CompilationTimeStamp]);
    BufferToLowerCase(Name, Length(Name));
    WerReportSetParameter(AWERReport, P3, AName3, @Name);
  end;

var
  ErrorCode: Cardinal;
  WERResult: Cardinal;
  WERReport: THandle;
  WERSubmitResult: TWERSubmitResult;
  ReportInfo: TWERReportInformation;
  PReportInfo: PWERReportInformation;
  EO: TObject;
  EA: Pointer;
  MBI: TMemoryBasicInformation;
  PClassName: PShortString;
  FriendlyEventName: array[0..128 - 1] of AnsiChar;
  Sz: Integer;
  Value: array[0..32] of WideChar;
  Module: HMODULE;
  Offset: HMODULE;
  Flags: Cardinal;
  WEI: TWERExceptionInformation;
  {$IFNDEF UNICODE}
  Description: array[0..512 - 1] of AnsiChar;
  {$ENDIF}

begin
  // Сначала определим код, с которым завершится наше приложение
  if AErrorCode <> 0 then
    ErrorCode := AErrorCode
  else
    ErrorCode := EP.ExceptionRecord.ExceptionCode;

  // Для Delphi исключений объект уже есть - извлекаем его
  if EP.ExceptionRecord.ExceptionCode = cDelphiException then
  begin
    EO := TObject(SysUtils.PExceptionRecord(EP.ExceptionRecord).ExceptObject);
    EA := SysUtils.PExceptionRecord(EP.ExceptionRecord).ExceptAddr;

    // На всякий случай проверим объект:
    // размещён ли он в доступной странице памяти.
    // Менеджер памяти Delphi может уже завершить работу к этому моменту.
    // Например, если исключение возникло в секции initialization модуля,
    // то сначала сработает блок except процедуры InitUnits модуля System,
    // который завершит модули (и менеджер памяти - в том числе),
    // а затем уже вызовет UnhandledExceptionFilter.
    if (EO = nil) or
       (VirtualQuery(EO, MBI, SizeOf(MBI)) = 0) or
       (MBI.State <> MEM_COMMIT) then
      EO := nil;
  end
  else
  // Для аппаратных исключений объекта нет
  begin
    EO := nil;
    EA := EP.ExceptionRecord.ExceptionAddress;
  end;

  EP.ExceptionRecord.ExceptionAddress := EA;

  // Вызываем WER, сначала вариант Windows Vista+
  if Assigned(WerReportCreate) (* and (EO <> nil) { опционально } *) then
  begin
    if EO <> nil then
    begin
      FillChar(ReportInfo, SizeOf(ReportInfo), 0);
      ReportInfo.dwSize := SizeOf(ReportInfo);
      ReportInfo.hProcess := GetCurrentProcess;
      if Assigned(Application) and Assigned(Application.MainForm) then
        ReportInfo.hwndParent := Application.MainForm.Handle;

      // Записываем класс исключения и сообщение в "тип события" и "описание" соответственно
      // Ради этого весь сыр-бор и затевался
      PClassName := PShortString(PPointer(PAnsiChar(PPointer(EO)^) + vmtClassName)^); // PAnsiChar используется для адресной арифметики
      Sz := Byte(PClassName^[0]);
      Move(PClassName^[1], FriendlyEventName, Sz);
      MultiByteToWideChar(CP_UTF8, 0, @FriendlyEventName, Sz, @ReportInfo.wzFriendlyEventName, Length(ReportInfo.wzFriendlyEventName));

      if EO is Exception then
      begin
        {$IFDEF UNICODE}
        StrLCopy(ReportInfo.wzDescription, PWideChar(Exception(EO).Message), Length(ReportInfo.wzDescription));
        {$ELSE}
        if Length(Exception(EO).Message) > Length(Description) then
          Sz := Length(Description)
        else
          Sz := Exception(EO).Message;
        Move(Pointer(Exception(EO).Message)^, Description, Sz);
        MultiByteToWideChar(CP_ACP, 0, @Description, Sz, @ReportInfo.wzDescription, Length(ReportInfo.wzDescription));
        {$ENDIF}
      end;

      PReportInfo := @ReportInfo;
    end
    else
      PReportInfo := nil;

    if Succeeded(WerReportCreate(APPCRASH_EVENT, WerReportApplicationCrash, PReportInfo, WERReport)) then
    // Альтернативно, вместо WerReportApplicationCrash можно указывать:
    // WerReportApplicationHang - для зависаний и взаимных блокировок,
    // WerReportCritical - аналог WerReportApplicationCrash, но в отчёте показывает имя исполняемого файла вместо имени,
    // WerReportNonCritical - для не-ошибок (отправка в фоне/очереди, нет UI)
    try
      // Устанавливаем 8 параметров - аналогично стандартному отчёту WER
      // (В принципе, мы можем задавать параметры как угодно)

      // Первые три параметра характеризуют приложение
      WERSetModuleParameters(WERReport, MainInstance, WER_P0, WER_P1, WER_P2,
        'Application Name',  'Application Version',  'Application Timestamp');

      // Следующие три параметра характеризуют модуль, в котором произошёл вылет
      Module := FindHInstance(EA);
      WERSetModuleParameters(WERReport, Module,       WER_P3, WER_P4, WER_P5,
        'Fault Module Name', 'Fault Module Version', 'Fault Module Timestamp');

      // Последние два параметра характеризуют исключение
      StrLFmt(@Value, Length(Value) - 1, '%8.8x', [EP.ExceptionRecord.ExceptionCode]);
      BufferToLowerCase(@Value, Length(Value));
      WerReportSetParameter(WERReport, WER_P6, 'Exception Code', @Value);

      Offset := HMODULE(EP.ExceptionRecord.ExceptionAddress);
      if (Module <> 0) and (Offset > Module) then
        Offset := Offset - Module;
      StrLFmt(@Value, Length(Value) - 1, {$IFDEF CPUX64}'%16.16x'{$ELSE}'%8.8x'{$ENDIF}, [Offset]);
      BufferToLowerCase(@Value, Length(Value));
      WerReportSetParameter(WERReport, WER_P7, 'Exception Offset', @Value);

      // и добавляем свой девятый параметр (не имеет аналога в WER) - код из EOSError, EDatabaseError, ...
      StrLFmt(@Value, Length(Value) - 1, '%d', [ErrorCode]);
      WerReportSetParameter(WERReport, WER_P8, 'Error Code', @Value);

      // Добавляем к отчёту мини-дамп (опционально)
      FillChar(WEI, SizeOf(WEI), 0);
      WEI.pExceptionPointers := @EP;
      WEI.bClientPointers := True;
      WerReportAddDump(WERReport, GetCurrentProcess, GetCurrentThread, WerDumpTypeMiniDump { или WerDumpTypeHeapDump для большего покрытия }, @WEI, nil, 0);

      // Опционально к отчёту можно присоединить любую дополнительную информацию и файлы

      if CheckWin32Version(6, 2) then // Win8+ - добавим идентификатор машины (опционально)
        Flags := WER_SUBMIT_OUTOFPROCESS or WER_SUBMIT_REPORT_MACHINE_ID
      else
        Flags := WER_SUBMIT_OUTOFPROCESS;
      // "out of process" делать - хорошо, минимизирует вылет при stack overflow

      // В этом месте появится обычное окно WER "В программе - ошибка, идёт поиск решения"
      if Failed(WerReportSubmit(WERReport, WerConsentNotAsked, Flags, WERSubmitResult)) then
        WERSubmitResult := WerReportFailed;

      case WERSubmitResult of
        WerReportQueued, WerReportUploaded, WerDisabledQueue, WerReportAsync:
          // Успех
          TerminateProcess(GetCurrentProcess, ErrorCode);
        WerReportCancelled:
          // Отправку отменили, следовательно, UI был, выходим
          TerminateProcess(GetCurrentProcess, ErrorCode);
        WerReportDebug:
        begin
          // Нужно запустить отладчик из ключа реестра AeDebug
          // Оставляю это в качестве домашнего задания,
          // чтобы не засорять пример
        end;
      else begin end; // ошибки (WerDisabled, WerFailed)
      end;
    finally
      WerReportCloseHandle(WERReport);
    end;
  end
  // Не Vista, вызываем WinXP
  else if Assigned(ReportFault) then
  begin
    // В этом месте появится обычное окно WER "В программе - ошибка, идёт поиск решения"
    WERResult := ReportFault(EP, 0);

    case WERResult of
      frrvOk, frrvOkManifest, frrvOkQueued, frrvOkHeadless:
        // Успех
        TerminateProcess(GetCurrentProcess, ErrorCode);
      frrvCancelled:
        // Отправку отменили, следовательно, UI был, выходим
        TerminateProcess(GetCurrentProcess, ErrorCode);
      frrvLaunchDebugger:
      begin
        // Нужно запустить отладчик из ключа реестра AeDebug
        // Оставляю это в качестве домашнего задания,
        // чтобы не засорять пример
      end;
    else begin end; // ошибки frrvErr*, в том числе - frrvErrNoDW, если WER отключен
    end;
  end;

  // Если WER нет или произошла ошибка - покажем:
  if EO <> nil then
    // если объект есть - обычное сообщение средствами SysUtils
    ShowException(EO, EA)
  else
    // если объекта нет - своё сообщение
    ShowExceptionRecord(EP.ExceptionRecord.ExceptionCode, EP.ExceptionRecord.ExceptionAddress);

  // Завершим приложение в любом случае - с выбранным кодом ошибки
  TerminateProcess(GetCurrentProcess, ErrorCode);
end;

Да, ужасно много работы ради замены всего двух параметров. Получился не самый тривиальный код, который, к тому же работает без выделения памяти в куче стандартного менеджера памяти. Увы, вызвать WER — это вам не класс с перезаписью виртуального метода унаследовать. Вы можете модифицировать этот код под ваши нужды. Например, выводить имя класса и сообщение через (не используемые WER) параметры WER_P8 и WER_P9, или заменить один из стандартных параметров WER_P0-WER_P7.

И насчёт сообщения — будьте аккуратны. Параметры отчёта используются WER для группировки и классификации отчётов, а сообщение об ошибке может быть локализовано, что приведёт к ненужному дублированию отчётов. Экспериментируйте.

В любом случае, в результате имеем:

Для исключений Delphi теперь показывается имя класса и сообщение исключения. Для аппаратных исключений всё осталось по прежнему, дополнительной информации мы не добавляем.

Напомню, эта кастомизация возможна только на Windows Vista и выше. На Windows XP отчёт не изменится.

Смещение/адрес исключения

Как только вы опознали что именно произошло, осталось понять где это произошло. Для этого посмотрите на параметры имя модуля (не приложения!) и смещение (offset) для этого модуля.

Имя модуля покажет модуль, в котором было возбуждено исключение. Смещение — это смещение указателя (на код) от начала этого модуля. К примеру, если исключение возникло по адресу $004AF70A в модуле CrashMe5.exe, который загружен по (стандартному для .exe) адресу $00400000, то смещение будет равно: $004AF70A — $00400000 = $AF70A. И наоборот, если вы знаете смещение, то, узнав адрес загруженного модуля, можете узнать и где произошло исключение: $000AF70A + $00400000 = $004AF70A.

Почему вообще используется какое-то смещение? Почему бы в отчёте просто не указать адрес?

Потому что этот адрес не будет иметь для вас смысла. К примеру, на машине где произошёл вылет адрес оказался равен $77A0098E, а модуль был загружен по адресу $77990000. Ну а на вашей машине этот модуль оказался загруженным по адресу $5AD20000. И что теперь? Как вы найдёте место вылета? Адрес $77A0098E на вашей машине вообще не указывает внутрь модуля!

Но если вы знаете смещение ($77A0098E — $77990000 = $7098E), то легко определите и адрес вылета: $5AD20000 + $7098E = $5AD9098E — именно в этом месте произошло исключение.

Окей, но как же узнать, что за код выполнялся по этому адресу?

Для этого вы можете использовать утилиту Address Lookup. Она входит в состав трейсера исключений EurekaLog. Если у вас нет EurekaLog, то вы можете скачать бесплатный Tools Pack. Запустите утилиту, укажите на модуль вылета и укажите смещение:

Утилита покажет вам в каких модуле, классе, методе/функции и строке произошёл вылет. Чтобы это стало возможным утилите необходима отладочная информация. Она понимает множество форматов: map-файлы, TDS/TD32, JCL, DBG и другие. Но это также означает, что вам нужно заранее позаботится об этом. Включите генерацию map-файла (или используйте любой другой из поддерживаемых форматов). Разумеется, эти map-файлы (а также .tds, .dbg, .jdbg и т.п.) нужно будет хранить у себя. И не перепутать два разных файла от двух разных версий одной программы. Чтобы не перепутать — используйте поле версии в версионной информации модуля.

Некоторые форматы поддерживают внедрение (собственный формат EurekaLog, TDS/TD32, JCL и т.п.) — вы можете использовать их. Тогда дополнительных файлов хранить не нужно (только сам модуль) и нельзя перепутать версии файлов, что тоже есть хорошо. Но увеличивается размер модуля, что не есть хорошо. Выбор за вами.

Альтернативно, вы можете использовать и саму Delphi. Для этого запустите программу (ровно той версии, что упомянута в отчёте) и поставьте её на паузу (Run / Pause), затем откройте окно со списком модулей: View / Debug / Modules и выясните по какому адресу оказался загружен модуль на вашей машине.

У вас есть базовый адрес, у вас есть смещение — вычислите полный адрес, сложив два значения. Теперь используйте Search / Go to address и введите полученный абсолютный адрес:

Если вы всё сделали правильно, то IDE откроет нужный модуль и ткнёт вас в строчку с ошибкой.

Однако у этого способа куча минусов. Во-первых, не только версия программы должна полностью совпадать с модулем, в котором произошёл вылет, вам также необходимо иметь копию всех исходников для этой версии, а также быть 100% уверенными, что при перекомпиляции вы получите точно ту же самую программу. В противном случае адреса будут отличаться и этот метод ткнёт вас в неверное место.

Во-вторых, IDE попросту не поддерживает сторонние отладочные форматы. Поэтому если вылет произошёл во внешнем модуле (в частном случае — в модуле ОС), то IDE откроет CPU-отладчик и ткнёт вас в конкретную ассемблерную инструкцию. Никакой дополнительной информации она показать не сможет.

На практике хранить все копии исходников, восстанавливать их для исследования каждого вылета, пересобирать программу, гарантировать/проверять её совпадение — крайне трудоёмко и чревато ошибками, поэтому этот способ (с IDE) стоит приберечь только на самый последний случай (когда заранее к нему не подготовились), а сильно предпочтительнее просто генерировать отладочную информацию в одном из форматов и хранить её (либо ещё лучше — внедрять), а затем использовать Address Lookup.

P.S. Аналогичную технику можно использовать и для анализа любых других адресов в сообщениях об ошибках. Например, «Runtime error 217 at 004060CD» или «Access Violation at address 005D2500…». К сожалению, Delphi сделана в этом отношении не совсем грамотно: она показывает абсолютный адрес, а не смещение. В итоге получается, что если ошибка произошла в .exe — вам (возможно) повезло: .exe (почти) всегда грузится по фиксированному адресу $00400000. Если же исключение произошло в DLL — вы пролетели, если только вам сильно не повезёт (каким-то образом вы узнаете базовый адрес DLL, или же DLL на вашей машине окажется загруженной по тому же адресу).

Стек, переменные и другая информация

Окей, с базовой информацией мы разобрались. Но что делать с самой вкусной частью пирога — стеком вызова и, возможно, значениями переменных? К сожалению, в Delphi вы не сделаете ничего. Но вы можете использовать отладчик Microsoft. Для этого вам потребуется следующее:

  1. Vista и выше. На Windows XP дампы не создаются.
  2. Отчёт вылета должен содержать в себе дамп процесса (дамп процесса также называется мини-дампом, противопоставляя себя полному дампу режима ядра). Дамп процесса — это «слепок» памяти и, возможно, объектов процесса. Если вы вызываете WER вручную, то параметры создания дампа вы указываете самостоятельно — в вызове WER. См. код-пример выше, где мы вызывали WerReportAddDump для добавления мини-дампа в отчёт. Если вылет происходит под управлением системы, то мини-дамп создаётся (или нет) согласно настройке WER. В любом случае, файл мини-дампа будет упомянут в отчёте (файл .mdmp или, реже, .dmp), а ссылки на локальные хранилища я приводил выше (в описании WER).
  3. Отладочная информация в формате, которую может понять отладчик Microsoft. Для этого вам нужно создавать отладочную информацию в формате MAP и/или TDS/TD32, а затем использовать конвертер в DBG или PDB. Для этого подойдёт какой-либо из вариантов утилиты map2dbg:
    • https://code.google.com/p/map2dbg/
    • https://github.com/andremussche/map2dbg
    • https://github.com/garethm/map2dbg
    • https://bitbucket.org/wpostma/map2dbg

    или утилиты tds2pdb:

    • https://github.com/andremussche/map2dbg/tree/master/tds2pdb
    • https://sourceforge.net/projects/tds2pdb/
  4. Сама Visual Studio или WinDbg.

Я решил вынести этот вопрос за рамки статьи, потому что я не уверен, что эта информация нужна по следующим причинам:

  • Это достаточно трудоёмкий процесс.
  • Интеграция не идеальна. К примеру, Visual Studio мне так и не удалось заставить читать DBG/PDB, созданные указанными утилитами. Это работает для WinDbg, но для Delphi-ста изучать WinDbg (отладчик, управляемый в основном текстовыми командами) — весьма нетривиальная задача. Также есть проблемы с 64-разрядными файлами.
  • Намного проще добавить к отчёту произвольный файл(ы) (через WerReportAddFile) — куда вы можете записать стеки, дампы кучи, переменные, логи, скриншоты, рабочие файлы и вообще всё, что душа пожелает.

Как мне получать отчёты WER?

Для этого необходимо зарегистрировать в Microsoft свою программу (а точнее — каждую версию каждого исполняемого файла).

В первую очередь для этого вам потребуется сертификат для цифровой подписи — только так вы можете подтвердить, что вы именно тот человек, который собрал программу. Ранее для этого требовался только особый сертификат от одобренного Microsoft поставщика: VeriSign — стоимостью $500 в год.

К счастью, сегодня это не так. Вам подойдёт любой сертификат для цифровой подписи. Ключевые слова для поиска: code signing certificate или Microsoft Authenticode. Стоимость — что-то от $65 в год (при покупке на несколько лет) до $200 — в зависимости от поставщика. Остались и сертификаты за $500, но они теперь уже EV: «Extended Validation». Для них производится более тщательная проверка перед выдачей сертификата вас как компании. Такие подписи получают бонус от Smart-фильтров браузеров, а также требуются для подписи драйверов. Для обычного же разработчика вполне достаточно сертификатов за $65 в год. Только убедитесь, что вы покупаете сертификат именно для подписи исполняемых файлов Windows.

Подписывать свои программы — есть хорошо в любом случае, так что, если вы профессионально разрабатываете софт, то сертификат для подписи у вас и так уже есть.

Как только у вас появится сертификат, вам, возможно, придётся переконвертировать в подходящий формат. Это бывает редко, но иногда необходимо. Поскольку я понятия не имею, в каком формате у вас сертификат — идите в Google. Вам нужно сконвертировать в формат PFX.

Далее, вам потребуется Windows SDK. Не хочу давать прямую ссылку, они меняются. Поищите в Google по ключевым: download Windows SDK. Устанавливаете SDK. Вас там интересует только утилита SignTool.

Когда SDK поставили — можно подписывать файлы. Делать это нужно после каждой компиляции для релиза. Можете использовать для этого Post-Build Event в современных IDE, либо что-то вроде FinalBuilder.

Если вы не используете FinalBuilder, то вызывать SignTool вам придётся вручную. Как-то так:
"C:Program Files (x86)Windows Kits8.0binx86signtool.exe" sign /f "C:путь-к-сертификатусертификат.pfx" /p "пароль" /tr timestamp-сервер "C:путь-к-программемодуль.exe" /d "Краткое описание программы" /du "http://сайт-программы/"
где «timestamp-сервер» может быть: http://timestamp.comodoca.com, http://sha256timestamp.ws.symantec.com/sha256/timestamp, http://tsa.starfieldtech.com/ или любым другим timestamp-сервером, поддерживающим RFC 3161.

Примечание: все современные сертификаты используют SHA-256 и не распознаются Windows XP и ниже (которая поддерживает только SHA-1).

Как только вы подписали файл — можно регистрировать его в Microsoft. Веб-сайт Windows Error Reporting (бывший WinQual) сейчас переехал на sysdev.microsoft.com (который, в свою очередь, в настоящее время находится в процессе переезда на https://developer.microsoft.com/dashboard/hardware — который пока только для разработчиков драйверов). Само собой, потребуется учётная запись Microsoft (Live), а также необходимо будет использовать только Internet Explorer.

Регистрация происходит путём подписи файла. Вам предложат скачать .exe файл, вы должны подписать его своим сертификатом и закачать на сайт. Профиль компании будет создан автоматически по сертификату, вы станете администратором. Если же доступа к сертификату у вас нет, то вы можете выбрать одну из уже зарегистрированных компаний и сможете присоединиться к ней после одобрения заявки администратором компании.

Выглядит это примерно так (на скриншотах показан процесс для драйверов на developer.microsoft.com, поэтому и упоминается EV; я не могу сделать скриншоты для обычного сертификата, поскольку этот процесс я уже прошёл; напомню, что в настоящее время вам нужно заходить на sysdev.microsoft.com, где произойдёт аналогичный процесс):

Как только вы успешно зарегистрировались и вошли — вам будет доступна консоль (dashboard).

Большинство опций в ней предназначены для разработчиков драйверов и сертификации приложений — и потому нам не интересны. Фактически, нас интересуют только отчёты (Reports). Но прежде нам нужно сделать ещё две вещи.

Первое, что вам придётся в ней сделать — подписать соглашение. Для этого откройте Administration / Legal agreements:

Как и ранее — тут куча всего для драйверов и сертификации, что нам не интересно. Выберите в фильтре тип «Signable» (т.е. что можно подписать, но ещё не подписано) и найдите в списке WER. Я не могу показать процесс подписи именно для этого соглашения (потому что у меня оно уже подписано), но вот скриншот для другого соглашения:

Важно: вы должны использовать только Internet Explorer и у вас должен стоять Adobe Acrobat.

Фактически, тут вам нужно продублировать ваше имя и дату (Ctrl + C / Ctrl + V) и нажать «Submit».

Наконец, последнее, что нам осталось сделать — зарегистрировать свои программы. Делается это не тривиально. Откройте раздел Reports (где будет написано, что у вас отчётов нет) и проскрольте вниз, ищите ссылку на «Product mapping Tool»:

Качайте и устанавливайте.

Тут вас поджидает сюрприз, ибо установщик запросит установленный Windows Live Essentials, который… не доступен для скачивания. Придётся искать оффлайн установщик по неофициальным каналам. Короче, гуглите.

В любом случае, текущая версия Product Mapping Tool называется аж Microsoft Ecosystem Metadata Exchange (или MEME). Она попросит вас войти в вашу учётку и синхронизируется с сервером Microsoft.

Ну вот с помощью её мы и добавляем каждый выпуск (release) нашей программы. Само собой, добавляем Product, а не Driver. Ну и если у вас прям сложная программа, то можете создать ещё Product Group, но это не обязательно.

Не забываем добавить и сами файлы (которые уже должны быть подписаны вашим сертификатом). Ещё раз: файл обязан быть подписан! И быть подписан именно вашим сертификатом, на аккаунт которого вы вошли в MEME. Если файл не будет подписан или будет подписан любым другим сертификатом (включая двойную подпись) — MEME покажет ошибку.

Ещё один сюрприз тут — утилиту нужно вручную запускать с правами администратора (запускать из: C:Program Files (x86)Microsoft Ecosystem Metadata ExchangeMetadataExchange). По крайней мере, на Windows 10. Иначе попытка добавления файла закончится ошибкой недостаточных привилегий (утилита выдаст общее сообщение «ошибка — см. лог», а подробности можно найти в системном логе «Приложения»).

Я понятия не имею, что будет, если версионная информация в исполняемом модуле не будет совпадать с мета-информацией, которую вы вводите в MEME. Лучше бы, наверное, чтобы она совпадала как можно лучше — особенно номера версий.

Не забудьте опубликовать внесённые изменения — ссылка «Publish Changes» в заголовке:

Публикуйтесь — и на этом всё! Конечно, процесс ассоциации нужно будет проводить заново для каждого релиза/версии каждой вашей программы. Но по крайней мере, вы уже зарегистрированы в системе.

После этого можно деплоить и крашиться! После регистрации вид раздела Reports изменится на рабочий:

Учтите только, что отчётам нужно время, чтобы пройти отправку и сортировку, появляются они не сразу. Задержка будет от нескольких дней до месяца.

Вот как будет выглядеть раздел отчётов при поступлении багов:

Это общий вид «Product group summary», где создано две версии одного продукта, одна из которых раскрыта. Как вы можете видеть, отчёты сортируются по количеству возникновения. Чем больше произошло вылетов — тем выше в списке запись. Есть также «Company hotlist» — куда попадают самые «популярные» быги из всех ваших продуктов. Подразумевается, что «Company hotlist» будет служить своеобразным списком TO-DO для исправления багов.

Вы можете раскрыть любую запись:

Будут показана статистика и возможность скачать отчёты.

У записей также может не быть никаких данных, кроме статистики. Это потому, что они не собраны. Вы должны щёлкнуть «Request more data»:

В любом случае, когда вы скачаете отчёт (.cab или .zip-файл — в зависимости от ОС), в нём будет:

Вот два отчёта с разных систем. В обоих случаях присутствует дамп процесса (minidump.mdmp), общая информация (WERInternalMetadata.xml) и пользовательский файл, присоединённый через WerReportAddFile (dump.txt) — куда вы можете сохранить информацию об исключении, стеки вызовов и т.п.

WERInternalMetadata.xml выглядит примерно так (полный вариант из отчёта с 6-ю файлами; вариант из отчёта с тремя файлами существенно короче):

<?xml version="1.0" encoding="UTF-16"?>
<WERReportMetadata>
 <OSVersionInformation>
  <WindowsNTVersion>10.0</WindowsNTVersion>
  <Build>15063</Build>
  <Product>(0x30): Windows 10 Pro</Product>
  <Edition>Professional</Edition>
  <BuildString>15063.138.amd64fre.rs2_release.170317-1834</BuildString>
  <Revision>138</Revision>
  <Flavor>Multiprocessor Free</Flavor>
  <Architecture>X64</Architecture>
  <LCID>1049</LCID>
 </OSVersionInformation>
 <ProcessInformation>
  <Pid>11200</Pid>
  <ImageName>CrashMe32.exe</ImageName>
  <CmdLineSignature>00000000</CmdLineSignature>
  <Uptime>2114</Uptime>
  <Wow64 guest="332" host="34404">1</Wow64>
  <ProcessVmInformation>
   <PeakVirtualSize>151908352</PeakVirtualSize>
   <VirtualSize>142888960</VirtualSize>
   <PageFaultCount>5762</PageFaultCount>
   <PeakWorkingSetSize>21839872</PeakWorkingSetSize>
   <WorkingSetSize>14458880</WorkingSetSize>
   <QuotaPeakPagedPoolUsage>270200</QuotaPeakPagedPoolUsage>
   <QuotaPagedPoolUsage>249184</QuotaPagedPoolUsage>
   <QuotaPeakNonPagedPoolUsage>17256</QuotaPeakNonPagedPoolUsage>
   <QuotaNonPagedPoolUsage>16712</QuotaNonPagedPoolUsage>
   <PagefileUsage>4116480</PagefileUsage>
   <PeakPagefileUsage>4124672</PeakPagefileUsage>
   <PrivateUsage>4116480</PrivateUsage>
  </ProcessVmInformation>
  <ParentProcess>
   <ProcessInformation>
    <Pid>2464</Pid>
    <ImageName>TOTALCMD64.EXE</ImageName>
    <CmdLineSignature>80004005</CmdLineSignature>
    <Uptime>4467447</Uptime>
    <Wow64 guest="0" host="34404">0</Wow64>
    <ProcessVmInformation>
     <PeakVirtualSize>657219584</PeakVirtualSize>
     <VirtualSize>461492224</VirtualSize>
     <PageFaultCount>28170</PageFaultCount>
     <PeakWorkingSetSize>44371968</PeakWorkingSetSize>
     <WorkingSetSize>38064128</WorkingSetSize>
     <QuotaPeakPagedPoolUsage>502208</QuotaPeakPagedPoolUsage>
     <QuotaPagedPoolUsage>476984</QuotaPagedPoolUsage>
     <QuotaPeakNonPagedPoolUsage>35848</QuotaPeakNonPagedPoolUsage>
     <QuotaNonPagedPoolUsage>33168</QuotaNonPagedPoolUsage>
     <PagefileUsage>19787776</PagefileUsage>
     <PeakPagefileUsage>20017152</PeakPagefileUsage>
     <PrivateUsage>19787776</PrivateUsage>
    </ProcessVmInformation>
   </ProcessInformation>
  </ParentProcess>
 </ProcessInformation>
 <ProblemSignatures>
  <EventType>APPCRASH</EventType>
 </ProblemSignatures>
 <DynamicSignatures>
  <Parameter1>10.0.15063.2.0.0.256.48</Parameter1>
  <Parameter2>1049</Parameter2>
 </DynamicSignatures>
 <SystemInformation>
  <MID>6A93F14F-F67E-426D-AC92-FA8CC504DDFC</MID>
  <SystemManufacturer>System manufacturer</SystemManufacturer>
  <SystemProductName>System Product Name</SystemProductName>
  <BIOSVersion>4802</BIOSVersion>
  <OSInstallDate>1492177385</OSInstallDate>
  <OSInstallTime>2017-04-14T13:42:56Z</OSInstallTime>
  <TimeZoneBias>-03:00</TimeZoneBias>
 </SystemInformation>
 <SecureBootState>
  <UEFISecureBootEnabled>1</UEFISecureBootEnabled>
  <PolicyPublisher>{77fa9abd-0359-4d32-bd60-28f4e78f784b}</PolicyPublisher>
 </SecureBootState>
 <ReportInformation>
  <Guid>b91edf68-85d7-42b4-9805-d07cba17fec0</Guid>
  <ReportSource>memory</ReportSource>
  <CreationTime>2017-04-25T20:03:11Z</CreationTime>
 </ReportInformation>
</WERReportMetadata>

sysinfo.txt содержит статистику работы системы (что-то вроде performance counters). WERDataCollectionStatus.txt — служебный. А memory.csv содержит список процессов (со статистикой).

Что если я хочу использовать отчёты, но не хочу использовать WER?

Да, использовать WER для Delphi приложений довольно сложно:

  1. Достаточно сложно всё настроить и зарегистрироваться
  2. Требуется сертификат цифровой подписи
  3. Отчёты доставляются с задержкой, а сам сайт sysdev работает неторопливо
  4. Нужно явно регистрировать каждую публикуемую сборку приложения
  5. Отчёты с Windows XP (и ранее) практически бесполезны, поскольку не включают в себя дамп и не позволяют приложить пользовательские файлы
  6. Невозможно использовать отладчик Delphi или Visual Studio для анализа дампов, будет работать только WinDbg
  7. Невозможно анализировать дампы 64-разрядных процессов
  8. В случае неконтролируемого вылета (т.е. когда WER вызывает система, а не мы сами), пользовательских данных у отчёта не будет

Поэтому вы можете захотеть использовать свой собственный механизм отправки и сбора отчётов. Для этого вам потребуется две вещи:

  1. Централизованный сервер для сбора отчётов, включая их сортировку/группировку и возможности просмотра
  2. Код отправки вылета приложения на сервер

В качестве сервера я крайне рекомендую FogBugz или Exceptionless — это единственные известные мне системы отслеживания ошибок (среди прочих: Mantis, JIRA, Bugzilla, Redmine, YouTrack), которые были созданы с прицелом на автоматический сбор отчётов. Только FogBugz и Exceptionless поддерживают понятие bug hash / bugID / alias с автоматической группировкой, учётом (count / occurencies) и контролем сбора. Во всех остальных трекерах отсутствует часть возможностей. Будете делать отправку отчётов через REST API FogBugz — ищите по ключевым словам BugzScout и sScoutDescription. У Exceptionless API ещё проще, но слабо документирован — проще посмотреть их примеры и перехватить трафик.

К счастью, мы уже рассмотрели все модификации кода, которые вам нужно сделать в вашем приложении выше — теперь вам осталось только поместить ваш код отправки отчёта вместо ReportFault / WerSubmitReport в функцию UnhandledExceptionHandler выше.

Что если я не хочу изобретать при этом велосипед?

Тогда вам нужно использовать готовое решение. Для Delphi есть два трейсера исключений с поддержкой отправки отчётов в FogBugz: EurekaLog и madExcept. Конечно, они поддерживают не только FogBugz, но и другие трекеры.

Более того, если вы хотите использовать WER, то EurekaLog поддерживает и его — присоединяя свой отчёт как дополнительный файл. (Я не уверен, но возможно, что madExcept в режиме Windows Logo Compliant Mode делает что-то аналогичное.)

Ну и само собой, трейсеры исключений дадут вам и контекст процессора и стек вызовов.

Дополнительные материалы для чтения

Что можно почитать:

  • Windows Error Reporting — обсуждение WER API в Windows:
    • Using WER
    • Windows Feedback Services
    • WER Reference
  • Configuring Automatic Debugging — администрирование WER и посмертного отладчика:
    • Enabling Postmortem Debugging
    • WER Settings
    • Collecting User-Mode Dumps
    • How to: Configure Microsoft Error Reporting
  • Windows Error Reporting: Getting Started — обсуждение использования сайта WER / WinQual / SysDev:
    • How WER collects and classifies error reports
    • Providing Solutions to Reported Errors

P.S. Блиииин, как же я увлёкся. А ведь просто хотел написать про регистрацию в WER.

Служба WER (Windows Error Reporting) служит для сбора и отправки отладочной информации о падении системных и сторонних приложений в Windows на сервера Microsoft. По задумке Microsoft, эта информация должна анализироваться и при наличии решения, вариант исправления проблемы должен отправляется пользователю через Windows Error Reporting Response. Но по факту мало кто пользуется этим функционалом, хотя Microsoft настойчиво оставляет службу сбора ошибок WER включенной по умолчанию во всех последних версиях Windows. В большинстве случае о службе WER вспоминают, когда каталог C:ProgramDataMicrosoftWindowsWERReportQueue начинает занимать на системном диске довольно много места (вплоть до нескольких десятков Гб).

Служба Windows Error Reporting

Служба Windows Error Reporting представляет собой отдельный сервис Windows, который можно легко отключить командой:

net stop WerSvc

Внутри каталога WERReportQueue содержится множество каталогов, с именами в формате:

  • Critical_6.3.9600.18384_{ID}_00000000_cab_3222bf78
  • Critical_powershell.exe_{ID}_cab_271e13c0
  • Critical_sqlservr.exe__{ID}_cab_b3a19651
  • NonCritical_7.9.9600.18235__{ID}_0bfcb07a
  • AppCrash_cmd.exe_{ID}_bda769bf_37d3b403

Как вы видите, имя каталога содержит степень критичности события и имя конкретного exe файла, который завершился аварийно. Во всех каталогах обязательно имеется файл Report.wer, который содержит описание ошибок и несколько файлов с дополнительной информацией.

Очистка папки WERReportQueue в Windows

Как правило, размер каждой папки незначителен, но в некоторых случаях для проблемного процесса генерируется дамп памяти, который занимает довольно много места. На скриншоте ниже видно, что размер файла дампа memory.hdmp составляет около 610 Мб. Парочка таким дампов – и на диске исчезло несколько свободных гигибайт.

Чтобы очистить все эти ошибки и журналы штатными средствами, откройте панель управления и перейдите в раздел ControlPanel -> System and Security -> Action Center -> Maintenance -> View reliability history -> View all problem reports и нажмите на кнопку Clear all problem reports.

Для быстрого освобождения места на диске от файлов отладки, сгенерированных службой WER, содержимое следующих каталогов можно безболезненно удалить и руками.

  • C:ProgramDataMicrosoftWindowsWERReportArchive
  • C:ProgramDataMicrosoftWindowsWERReportQueue

Отключение Window Error Reporting в Windows Server 2012 R2 / 2008 R2

Отключить запись информации об ошибках Windows Error Reporting в серверных редакция Windows можно следующим образом:

Отключение функции сбора и отправки отчетов в Windows 10

В Windows 10 возможность отключить Error Reporting через GUI отсутствует. Проверить статус компонента можно в панели управления Система и безопасность ->Центр безопасности и обслуживания -> секция Обслуживание. Как вы видите, по умолчанию параметр Поиск решения для указанных в отчетах проблем включен (Control Panel -> System and Security -> Security and Maintenance -> Maintenance -> Check for solutions to problem reports).

Check for solutions to problem reports - windows 10

Отключить Windows Error Reporting в Windows 10 можно через реестр. Для этого в ветке HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting

Теперь еще раз проверим статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

отключить Поиск решения для указанных в отчетах проблем в Windows 10

Отключение Windows Error Reporting через групповые политики

Ведение журналов службой Windows Error Reporting можно отключить и через групповую политику. Она находится в разделе Computer Configuration/Administrative Templates/Windows Components/Windows Error Reporting (Компоненты Windows -> Отчеты об ошибках Windows). Для отключения сбора и отправки данных включите политику Disable Windows Error Reporting (Отключить отчеты об ошибках Windows).

групповая политика - Отключить отчеты об ошибках Windows

В результате сообщения об ошибках приложений в Windows перестанут формироваться и автоматически отправляться в Microsoft.

Windows Error Reporting (WER) is a Windows function that captures the data of software crashes and can report this information to software vendors via Microsoft’s Winqual service. In this Windows Error Reporting series, I will explain how WER works, how you can access the information in WER files, and how you can disable Windows Error Reporting.

  • Author
  • Recent Posts

Michael Pietroforte is the founder and editor in chief of 4sysops. He has more than 35 years of experience in IT management and system administration.

Action Center Check for Solutions

Windows Error Reporting has been available since Windows XP, although changes have been introduced in Vista and Windows 7. Whenever a Windows application crashes, a WER file is created, which contains valuable information that can help you analyze why the crash happened.

While software vendors have to sign up to Microsoft’s Winqual service to access the crash data from their customers, admins can access it by opening the .wer files, which are simple text files that Windows stores at different locations. In some cases, the problem description will help you to understand why an application crashed.

However, it is often only the developer who will really understands the contents of the .wer file. But, you can’t go wrong having a look at these files before you decide whether you want to enable or disable Windows Error Reporting (if you are worried that confidential data will be sent to third parties). Furthermore, you can also send the files to the support service of your software or hardware vendor in the hope that they can figure out what went wrong.

In Windows 7, Windows Error Reporting files can be stored in a subfolder somewhere deep down in the ProgramData or User directory. The name of the subfolder is simply WER, and the file extension is .wer.

You can use Windows Search or another desktop search tool to locate them all. However, the information in these .wer files can also be accessed through the Windows Action Center (Control PanelSystem and SecurityAction Center).

Action Center View Problems to Reports

You’ll find a list of all crash reports behind the link «View problems to report» in the Maintenance section. If you type «view problems» in the Windows Start Menu search prompt, you will probably get quicker access the Action Center applet. Clicking on «View technical details», will then display the information in the corresponding .wer file.

Action Center Problem Details

In Vista, you have to type «problems» in the Windows Start Menu search prompt and then click on «Problems and Reports and Solutions». The list of .wer files is behind the «View problem history» link. To view the contents of the .wer file, you have to right click on one of the entries.

In the next post of my Windows Error Reporting series, I will review the free tool AppCrashView, which has some additional useful features for accessing the information in .wer files.

Служба WER (Windows Error Reporting) служит для сбора и отправки отладочной информации о падении системных и сторонних приложений в Windows на сервера Microsoft. По задумке Microsoft, эта информация должна анализироваться и при наличии решения, вариант исправления проблемы должен отправляется пользователю через Windows Error Reporting Response. Но по факту мало кто пользуется этим функционалом, хотя Microsoft настойчиво оставляет службу сбора ошибок WER включенной по умолчанию во всех последних версиях Windows. В большинстве случае о службе WER вспоминают, когда каталог C:ProgramDataMicrosoftWindowsWERReportQueue
или
c:UsersAll UsersMicrosoftWindowsWERReportQueue
начинает занимать на системном диске довольно много места (вплоть до нескольких десятков Гб), даже не смотря на то что на этом каталоге по умолчанию включена NTFS компрессия.

Служба Windows Error Reporting

Служба Windows Error Reporting представляет собой отдельный сервис Windows, который можно легко отключить командой:

  net stop WerSvc

Внутри каталога WERReportQueue содержится множество каталогов, с именами в формате:

  • Critical_6.3.9600.18384_{ID}_00000000_cab_3222bf78
  • Critical_powershell.exe_{ID}_cab_271e13c0
  • Critical_sqlservr.exe__{ID}_cab_b3a19651
  • NonCritical_7.9.9600.18235__{ID}_0bfcb07a
  • AppCrash_cmd.exe_{ID}_bda769bf_37d3b403

Как вы видите, имя каталога содержит степень критичности события и имя конкретного exe файла, который завершился аварийно. Во всех каталогах обязательно имеется файл Report.wer, который содержит описание ошибок и несколько файлов с дополнительной информацией.

Очистка папки WERReportQueue в Windows

Как правило, размер каждой папки незначителен, но в некоторых случаях для проблемного процесса генерируется дамп памяти, который занимает довольно много места. На скриншоте ниже видно, что размер файла дампа memory.hdmp составляет около 610 Мб. Парочка таким дампов – и на диске исчезло несколько свободных гигибайт.

Чтобы очистить все эти ошибки и журналы штатными средствами, откройте панель управления и перейдите в раздел ControlPanel -> System and Security -> Action Center -> Maintenance -> View reliability history -> View all problem reports и нажмите на кнопку Clear all problem reports.

Для быстрого освобождения места на диске от файлов отладки, сгенерированных службой WER, содержимое следующих каталогов можно безболезненно удалить и руками.

  • C:ProgramDataMicrosoftWindowsWERReportArchive
  • C:ProgramDataMicrosoftWindowsWERReportQueue

Отключение Window Error Reporting в Windows Server 2012 R2 / 2008 R2

Отключить запись информации об ошибках Windows Error Reporting в серверных редакция Windows можно следующим образом:

Windows Server 2012 / R2 – Панель Управления -> System and Security -> Action Center -> раздел Maintenance -> Settings -> выберите опцию I don’t want to participate, and don’t ask me again

Windows Server 2008 R2 – откройте консоль Server Manager и промотайте список, перейдя в раздел Resources and Support. Нажмите на Turn Off Windows Error Reporting и выберите пункт I don’t want to participate, and don’t ask me again.

Отключение функции сбора и отправки отчетов в Windows 10

В Windows 10 возможность отключить Error Reporting через GUI отсутствует. Проверить статус компонента можно в панели управления Система и безопасность ->Центр безопасности и обслуживания -> секция Обслуживание. Как вы видите, по умолчанию параметр Поиск решения для указанных в отчетах проблем включен (Control Panel -> System and Security -> Security and Maintenance -> Maintenance -> Check for solutions to problem reports).

Отключить Windows Error Reporting в Windows 10 можно через реестр. Для этого в ветке HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

Теперь еще раз проверим статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

Отключение Windows Error Reporting через групповые политики

Ведение журналов службой Windows Error Reporting можно отключить и через групповую политику. Она находится в разделе Computer Configuration/Administrative Templates/Windows Components/Windows Error Reporting (Компоненты Windows -> Отчеты об ошибках Windows). Для отключения сбора и отправки данных включите политику Disable Windows Error Reporting (Отключить отчеты об ошибках Windows).

В результате сообщения об ошибках приложений в Windows перестанут формироваться и автоматически отправляться в Microsoft.

С ошибками в Windows знакомо большинство пользователей. Разработчики предусмотрели реакцию операционной системы на такие события, которая заключается в отсылке отчёта о неполадке на сервере Microsoft с целью её анализа и устранения в будущих обновлениях. Насколько хорошо программисты справляются с этой задачей, мы обсуждать не берёмся, но и сама эта служба далеко не безупречна, иногда приводя к зависаниям компьютера. Сегодня мы рассмотрим, как работает Windows Error Reporting и что делать, если она начинает грузить CPU и диск.

Windows problem reporting в Диспетчере задач

Тот факт, что служба WER может грузить компьютер, не удивителен – это достаточно сложный механизм, который должен уметь правильно реагировать на все возникающие проблемы, независимо от того, являются ли они системным сбоем или произошли во время работы пользовательских процессов.

Принцип действия службы можно описать следующим образом: когда возникает системный сбой, срабатывает специальный механизм, отлавливающий такие случаи (на техническом языке они называются необработанными исключениями). В этом случае записываются все возможные сопутствующие факторы (например, значения стека и регистров в момент возникновения исключения), после чего запускается компонента WER, занимающаяся анализом состояния аварийно завершившегося приложения. В её функции входит также и оповещение пользователя о проблеме. Обычно это процесс WerFault.exe, который запускается с полномочиями пользователя компьютера, и по настройкам по умолчанию именно он и выводит окно, сообщающее о возникновении сбоя.

Windows problem reporting

Если дефолтные настройки не менялись (а такая возможность для этой службы имеется), то созданный в результате работы анализирующего модуля отчёт с включёнными в него данными об ошибке (дамп памяти и небольшой файл в формате XML, в котором содержатся некоторые подробности типа версий DLL-библиотек, используемых аварийно завершившимся процессом) отправляется на сервера Microsoft.

После этого пользователь уведомляется о проблеме и ему предлагаются варианты действий, который можно предпринять для попытки решить эту проблему. Что, конечно же, не гарантирует нужный результат. Это же сообщение дублируется в Центре поддержки Windows. Наконец, записываются все необходимые данные о состоянии приложения и операционной системы в службу Reliability Monitor (переводится как «Монитор стабильности системы»).

ВНИМАНИЕ. Windows Error Reporting срабатывает не всегда. Обязательное условие – наличие хотя бы одного активного окна приложения, в котором произошёл сбой. Вернее, окно не обязательно должно быть активным, но должно реагировать на системные запросы, что не всегда возможно. Если интерактивности по отношению к ОС нет, сбой всё равно будет отображён в журнале, но пользователь никаких сообщений не получит. Чтобы просмотреть сведения об ошибке, ему придётся вручную его искать в Центре поддержки. Это сделано для того, чтобы не вводить в заблуждение пользователя при возникновении аварийных ситуаций, не связанных с работой приложения, то есть это могут быть фоновые системные процессы.

Как отключить Windows problem reporting

Нередко сбои в работе приложений приводят к его аварийному завершению, но, когда в дело вступает Служба оповещений об ошибках, она сама становится источником проблем. Если такие случаи возникают часто при выполнении однотипных задач (например, при вставке большого массива данных в документ), самое простое решение – отключить службу WER, раз уж она не справляется со своими прямыми обязанностями.

Рассмотрим основные способы отключения Windows problem reporting, если служба грузит диск, процессор и систему в целом.

Очистка папок службы WER

Дампы с описанием ошибок обычно небольшие, но иногда дамп памяти, который обязательно включается в файл memory.hdmp, может достигать значительных размеров, порядка многих сотен мегабайт. Если на системном диске не так много места, с десяток таких дампов могут попросту исчерпать свободное пространство, и очередному отчёту уже не будет места – вот вам и проблемы, и зависания.

Переход в Панель управления

Для очистки логов службы запускаем Панель управления и набираем в строке поиска текст «Просмотр всех отчетов», выбираем из списка пункт с соответствующим названием, и в новом окне жмём кнопку «Очистить все отчёты о проблемах».

Поиск «Просмотр всех отчетов»

Кнопка «Очистить все отчёты о проблемах»

Подтверждение удаления отчётов о проблемах

Ту же операцию можно выполнить вручную, очистив две папки, WERReportArchive и WERReportQueue, находящиеся в каталоге ProgramDataMicrosoftWindows, от всего содержимого (там могут быть сотни подкаталогов).

Папки ReportArchive и ReportQueue

Удаление содержимого папки ReportArchive

Наконец, имеется альтернативный вариант, позволяющий удалять только старые файлы из каталогов Windows Error Reporting, причём интервал в днях, задаётся пользователем. Это команды, выполняемые через PowerShell:

Get-ChildItem -Path 'C:ProgramDataMicrosoftWindowsWERReportArchive' -Recurse | Where-Object CreationTime -lt (Get-Date).AddDays(-30) | Remove-Item -force -Recurse
Get-ChildItem -Path 'C:ProgramDataMicrosoftWindowsWERReportQueue' -Recurse | Where-Object CreationTime -lt (Get-Date).AddDays(-30) | Remove-Item -force –Recurse

В данном примере будут удалены все логи старше одного месяца.

Отключение WER в Windows 7/8

Но очистка логов службы – это не совсем правильное решение, ведь со временем ситуация может повториться. Проще отключить службу, ведь толку от неё мало. Делается это следующим образом:

Точно таким же образом, используя Панель управления, можно отключить службу WER в серверных версиях Windows (2019/2016/2012R2).

Отключение вывода отчётов об ошибках через системный реестр

К сожалению, в «десятке» этот простой способ не работает. Здесь имеется возможность только проверить статус службы (вкладка «Система и безопасность», переход в пункт «Центр безопасности и обслуживания», выбор подпункта «Обслуживание»). Хотя сам параметр «Отчёта о проблемах» здесь имеется, и он находится во включённом состоянии, кнопка отключения службы здесь не предусмотрена – очевидно, намеренно, чтобы иметь возможность всегда получать отчёты об ошибках.

Тем не менее, если Windows Error Reporting время от времени грузит компьютер, нужное решение имеется – для отключения службы придётся править системный реестр.

Переход в Редактор реестра

Для этого заходим в ветку HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting.

Папка Windows Error Reporting в реестре

Кликаем по пустому месту в правом окне и создаём новый параметр (выбираем тип DWORD 32-битный), присваиваем этому параметру тип Disabled, а в поле «Значение» вбиваем 1.

Создание параметра DWORD в Windows Error Reporting

Параметр Disabled в папке Windows Error Reporting

Следующими командами можно отключить сбор и отправку логов об ошибках для некоторых или всех пользователей:

reg add "HKCUSoftwareMicrosoftWindowsWindows Error Reporting" /v "Disabled" /t REG_DWORD /d "1" /f
reg add "HKLMSoftwareMicrosoftWindowsWindows Error Reporting" /v "Disabled" /t REG_DWORD /d "1" /f

Теперь, если вы зайдёте в Панель управления для проверки статуса службы, вместо On вы увидите Off, то есть у нас получилось отключить службу, призванную решать проблемы, но иногда создающую их.

Отключение поиска решений для указанных в отчетах проблем

При желании этот статус можно продублировать на всех компьютерах в рамках одного домена, используя GPO.

Отключение вывода отчётов об ошибках через редактор групповой политики

Действительно, если у вас есть сеть с несколькими компьютерами (а в среднем офисе их может быть десяток-другой), вместо того, чтобы выполнять все эти манипуляции энное количество раз, можно воспользоваться проверенным средством – редактором групповых политик.

Команда gpedit.msc в Windows

Используя консоль «Выполнить», набираем команду gpmc.msc или gpedit.msc (первая – для редактора доменной GPO, вторая – для локальной). Ищем и открываем ветку Computer Configuration, затем выбираем подпункт Administrative Templates, заходим в ветку Windows Components, и наконец, кликаем по строке Windows Error Reporting.

Папка «Административные шаблоны»

Папка «Компоненты Windows»

Папка «Отчеты об ошибках Windows»

Если в правом окне напротив параметра Disable Windows Error Reporting стоит значение Disabled, кликаем по этому параметру и в открывшемся окне изменяем его значение на Enable (Включено).

Пункт «Отключить отчеты об ошибках Windows»

Отключение отчетов об ошибках Windows

Вот и всё, теперь это правило будет действовать на все компьютерах в рамках домена. Кстати, точно такая же политика имеется в разделе User Configuration.

Как видим, для отключения службы Windows problem reporting требуется выполнить минимальное количество манипуляций, и только в Windows 10 придётся править реестр.

История [ править | править код ]

Windows XP

Впервые служба была реализована компанией Microsoft в Windows XP. Список отчетов WER доступен для просмотра с помощью утилиты msinfo32 .

Windows Vista

В следующей версии ОС, Vista, служба регистрации ошибок Windows была улучшена, появились публичные API для того, чтобы приложения могли отправлять через механизм не только отчеты о критичных ошибках .

Обновленный WER смог составлять отчеты даже в сложных случаях, например, когда у процесса закончился стек, были нарушены PEB/TEB или куча и т.п. В более ранних версиях подобные ситуации приводили к аварийному останову процесса без составления отчета о падении.

Появился апплет Панели управления, «Problem Reports and Solutions», который хранит список ошибок приложений и системы.

Windows 7

В Windows 7 и Windows Server 2008 R2, апплет Панели управления «Отчеты об ошибках Windows» был заменен на раздел Обслуживание в Центре поддержки Windows.

Средство записи действий по воспроизведению неполадок – приложение, поставляющееся во все сборки Windows 7, позволяет записать действия, выполняемые пользователем во время возникновения ошибки, чтобы тестеры и разработчики смогли воспроизвести ситуацию для анализа и отладки.

Steps to disable Window 10 Error Reporting Service

Just like other versions, Windows 10 does have a slightly different graphical user interface for disabling error reports. Lower versions of Windows OS have error reporting under Action Center Setting. On Windows 10 it is the Security & Maintenance function requiring working with registries.

Method One: Use Command to disable Window 10 Error Report

It’s a simple and straightforward process. Here are the steps to follow:

Note:

In some cases, the Startup type which is close to disabled might appear as gray. That means the user needs admin rights to continue the process. Therefore, log out and log in as an administrator. Or locate the administrative command prompt by pressing Windows key+ X to select command prompt admin.

Method Two: Use the Registry Editor

Using the Registry Editor is another way to safely disable the Windows 10 Error Reporting Service. This second method for disabling Windows error reporting service includes tweaking the registry. But first, you need to check for error report issues:

Steps to disable Windows Error Reporting Service using the Registry Editor

Note:

It’s quite simple to disable and enable Windows 10 error reporting service anytime. And understanding how to use the Registry Editor or finder makes everything much simpler.

Что включают данные папочки?

  1. Local — папочка включает данные про установленный софт, временные файлы, историю различных происшествий в Windows. Теперь, нужно внимательно просмотреть все папочки и файлы. Мы ищем те приложения, которые уже деинсталлированы и не применяются. Также, можно посмотреть на дату появления папочки. Если очень старая, можно удалить. Кроме этого, советую регулярно очищать папочку Temp. Именно в ней находятся временные файлы. Данная папка может занимать приличный объём, а толку мало.

    Если какая-то папочка в ней или файл отказывается на данный момент удаляться, оставьте её в покое, так как этот софт сейчас используется;

  2. LocalLow – в папке находятся промежуточная информация, которая необходима, чтобы обновить софт. Она чем-то напоминает буфер хранения данных. Когда вы эту информацию уже использовали, данные можно деинсталлировать;
  3. Roaming – в ней сохраняются файлы людей, не имеющих авторизации в системе. Эту информацию можно перенести на флешь карту или иной ПК.

    Данную информацию можно назвать резервной копией программ.

Конфигурация обработчика диагностических данных Windows позволяет вам быть управляющим (в соответствии с определением в Общем регламенте Европейского Союза по защите данных (GDPR)) диагностическими данными Windows, собранными с ваших устройств Windows, которые соответствуют требованиям к конфигурации.

Предварительные условия

  • На устройстве должен использоваться любой из следующих выпусков Windows:
    • выпуск Windows 11 Корпоративная, Профессиональная или для образовательных учреждений
    • выпуск Windows 10 Корпоративная, для образовательных учреждений или Профессиональная версии 1809 с обновлением за июль 2021 г. либо более поздней.
  • Устройство должно быть присоединено к Azure Active Directory.

Для параметра диагностических данных на устройстве следует задать значение «Обязательные диагностические данные» или выше. Должны быть доступны следующие конечные точки:

  • v10c.events.data.microsoft.com
  • umwatsonc.events.data.microsoft.com
  • kmwatsonc.events.data.microsoft.com
  • settings-win.data.microsoft.com
  • *.blob.core.windows.net

Включение конфигурации обработчика диагностических данных Windows

Следуйте инструкциям ниже, чтобы включить конфигурацию обработчика диагностических данных Windows с помощью одного параметра, групповой политики или решения MDM.

Чтобы включить конфигурацию обработчика диагностических данных Windows средствами групповой политики, перейдите в раздел Конфигурация компьютера > Административные шаблоны > Компоненты Windows > Сбор данных и предварительные сборки и переключите параметр Разрешить конвейер коммерческих данных в положение Включено.

Если вы хотите отключить этот параметр, можно в любой момент переключить его в положение Отключено. Значением по умолчанию для этого параметра является Отключено.

Чтобы использовать решение MDM (например, Microsoft Intune) для развертывания конфигурации обработчика диагностических данных Windows на поддерживаемых устройствах, используйте следующую конфигурацию настройки OMA-URI.

  • Имя: System/AllowCommercialDataPipeline
  • OMA-URI: ./Vendor/MSFT/Policy/Config/System/AllowCommercialDataPipeline
  • Тип данных: целое число

В разделе Значение выберите 1, чтобы включить службу.

Если вы хотите ее отключить, можно в любой момент переключить параметр на . Значение по умолчанию — .

Примечание

  • Если у вас есть дополнительные политики, которые позволяют быть управляющим диагностическими данными Windows, например политики перечисленных ниже служб, необходимо отключить все применимые политики, чтобы перестать быть управляющим диагностическими данными Windows.
  • Диагностические данные Windows, собранные с устройства до включения с конфигурацией обработчика диагностических данных Windows, будут удалены при включении этой конфигурации.
  • Когда вы включите устройства с конфигурацией обработчика диагностических данных Windows, пользователи смогут и дальше отправлять отзывы через различные каналы, такие как Центр отзывов о Windows или о Microsoft Edge. Однако к данным отзывов не применяются условия конфигурации обработчика диагностических данных Windows. Если это нежелательно, рекомендуется отключить обратную связь с помощью доступных политик или решений для управления приложениями.

Вы также можете включить конфигурацию обработчика диагностических данных Windows, зарегистрировавшись в службах, которые используют диагностические данные Windows. В настоящее время к этим службам относятся Аналитика компьютеров, Поддержка обновлений, компьютеры, управляемые Майкрософт, и Центр обновления Windows для бизнеса.

Сведения об этих службах и настройке групповых политик можно найти в следующей документации:

Аналитика компьютеров:

  • Включение общего доступа к данным для Аналитики компьютеров
  • Конфиденциальность данных в Аналитике компьютеров
  • Параметры групповой политики для Аналитики компьютеров

Поддержка обновлений:

  • Конфиденциальность в Поддержке обновлений

Компьютеры, управляемые Майкрософт:

Конфиденциальность и персональные данные

Центр обновления Windows для бизнеса:

Как включить защиту развертывания

Отключение функции сбора и отправки отчетов в Windows 10

В Windows 10 возможность отключить Error Reporting через GUI отсутствует. Проверить статус компонента можно в панели управления Система и безопасность ->Центр безопасности и обслуживания -> секция Обслуживание. Как вы видите, по умолчанию параметр Поиск решения для указанных в отчетах проблем включен (Control Panel -> System and Security -> Security and Maintenance -> Maintenance -> Check for solutions to problem reports).

Отключить Windows Error Reporting в Windows 10 можно через реестр. Для этого в ветке HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

Теперь еще раз проверим статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

Как настроить регулирование энергопотребления / мощности в Windows 10

В Windows 10 пользователи, как правило, работают с несколькими приложениями. В результате программы, которые работают в фоновом режиме, потребляют значительное количество энергии.

Чтобы оптимизировать энергопотребление, в начиная с Windows 10 (версия 1709) компания Microsoft представила новую функцию Power Throttling (регулирование энергопотребления или регулирование мощности), которая использует технологии энергосбережения современных процессоров для ограничения ресурсов для фоновых процессов.

Используя данную технологию, система Windows может автоматически определять, какие приложения вы активно используете, и и ограничивать ресурсы для процессов, которые неважными. Функция регулирования мощности позволяет увеличить автономную работу устройства до 11 процентов.

Однако, распознавание процессов может работать не так, как ожидается. В этих случаях пользователь может самостоятельно контролировать, каким приложениям нужно ограничить потребление ресурсов, а каким нужно предоставить всю доступную мощность.

Как посмотреть, какие процессы регулируются

Чтобы посмотреть, какие процессы регулируются функцией Power Throttling можно воспользоваться Диспетчером задач.

  1. Щелкните правой кнопкой мыши по панели инструментов и выберите пункт “Диспетчер задач”.
  2. Перейдите на вкладку “Подробности”.
  3. Щелкните правой кнопкой мыши по заголовку любого столбца и выберите пункт “Выбрать столбцы”.
  4. Прокрутите вниз и отметьте галочку Регулирование энергопотребления.
  5. Нажмите ОК.

После выполнения данных шагов появится новый столбец “Регулирование мощности”, показывающий, какие процессы находятся в энергосберегающем режиме.

На ноутбуках, планшетах или других портативных устройствах с аккумулятором у некоторых процессов будет указан статус «Регулирование энергопотребления» – “Включено”, а у остальных – “Выключено”.

Наглядно посмотреть новую функцию в действии можно открыв приложение, а затем свернув его. Когда вы активно используете приложение в Диспетчере задач будет показываться статус регулирования энергопотребления “Выключено”, но после сворачивания статус изменится на “Включено”.

Если все процессы имеют статус “Выключено”, значит ваше устройство подключено к источнику питания или используются режим Максимальная производительность.

Как отключить регулирование мощности в настройках электропитания

Power Throttling активируется автоматически, когда портативное устройство не заряжается, а это означает, что самый быстрый способ отключить эту функцию – подключить устройство к источнику питания.

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

  • Экономия заряда – регулирование мощности включается
  • Улучшенная батарея – регулирование мощности включается
  • Оптимальная производительность – регулирование мощности включается, но в щадящем режиме
  • Максимальная производительность – регулирование мощности отключается

Как отключить регулирование мощности с помощью редактора групповых политик

Если вы используете Windows 10 Pro, то отключить Power Throttling можно с помощью редактора групповых политик.

  1. Используйте сочетание клавиша Windows + R , чтобы открыть окно команды “Выполнить”.
  2. Введите gpedit.msc и нажмите ОК, чтобы открыть редактор локальной групповой политики.
  3. Перейдите по пути: Конфигурация компьютера > Административные шаблоны > Система > Управление электропитанием > Параметры регулирования мощности
  4. Щелкните дважды по политике Выключить регулирование мощности.
  5. Выберите опцию “Включено”, нажмите “Применить” и “ОК”.
  6. Перезагрузите компьютер.

После завершения данных шагов и перезагрузки ПК, регулирование мощности будет отключено для всех приложений в любых режимах электропитания.

В любое время вы можете снова активировать регулирование мощности, выполните те же шаги, но в пункте 5 выберите опцию “Не задано”.

Как отключить регулирование мощности с помощью системного реестра

Редактор групповых политик недоступен в Windows 10 Домашняя, но вы можете сделать то же самое с помощью редактора реестра.

Очистка папки WERReportQueue в Windows

Как правило, размер каждой папки незначителен, но в некоторых случаях для проблемного процесса генерируется дамп памяти, который занимает довольно много места. На скриншоте ниже видно, что размер файла дампа memory.hdmp составляет около 610 Мб. Парочка таким дампов – и на диске исчезло несколько свободных гигибайт.

Чтобы очистить все эти ошибки и журналы штатными средствами, откройте панель управления и перейдите в раздел ControlPanel -> System and Security -> Action Center -> Maintenance -> View reliability history -> View all problem reports и нажмите на кнопку Clear all problem reports.

Для быстрого освобождения места на диске от файлов отладки, сгенерированных службой WER, содержимое следующих каталогов можно безболезненно удалить и руками.

  • C:ProgramDataMicrosoftWindowsWERReportArchive
  • C:ProgramDataMicrosoftWindowsWERReportQueue

Что удалить в очистке диска

Это не полное руководство по инструменту очистки диска, поэтому мы не будем рассматривать все варианты, которые он предлагает. Тем не менее, следующие несколько опций являются обязательными (не забудьте выбрать Очистить системные файлы, чтобы увидеть их все):

  • Очистка Центра обновления Windows: стирает старые копии файлов Центра обновления Windows. Их можно безопасно удалить в большинстве случаев, но вы должны сохранить их для устранения неполадок, если вы столкнетесь с проблемами, связанными с обновлением.
  • Файлы журнала обновления Windows: аналогично, эти файлы данных хранятся в Центре обновления Windows, чтобы помочь вам разобраться в проблемах вокруг них. Вы можете удалить их, если у вас не было ошибок, связанных с обновлением Windows.
  • Файлы языковых ресурсов: если вы ранее загрузили другой язык или раскладку клавиатуры, которую вы не используете, это позволит вам легко стереть ее.
  • Корзина: Хотя вы можете очистить корзину через ее окно, вы также можете легко сделать это здесь.
  • Временные файлы. Как следует из их названия, временные файлы ни для чего не используются в долгосрочной перспективе, поэтому вы можете стереть их, не беспокоясь.

Служба Windows Error Reporting

Служба Windows Error Reporting представляет собой отдельный сервис Windows, который можно легко отключить командой:

net stop WerSvc

Внутри каталога WERReportQueue содержится множество каталогов, с именами в формате:

  • Critical_6.3.9600.18384__00000000_cab_3222bf78
  • Critical_powershell.exe__cab_271e13c0
  • Critical_sqlservr.exe___cab_b3a19651
  • NonCritical_7.9.9600.18235___0bfcb07a
  • AppCrash_cmd.exe__bda769bf_37d3b403

Как вы видите, имя каталога содержит степень критичности события и имя конкретного exe файла, который завершился аварийно. Во всех каталогах обязательно имеется файл Report.wer , который содержит описание ошибок и несколько файлов с дополнительной информацией.

Очистка папки WERReportQueue в Windows

Как правило, размер каждой папки незначителен, но в некоторых случаях для проблемного процесса генерируется дамп памяти, который занимает довольно много места. На скриншоте ниже видно, что размер файла дампа memory.hdmp составляет около 610 Мб. Парочка таким дампов – и на диске исчезло несколько свободных гигибайт.

Чтобы очистить все эти ошибки и журналы штатными средствами, откройте панель управления и перейдите в раздел ControlPanel -> System and Security -> Action Center -> Maintenance -> View reliability history -> View all problem reports и нажмите на кнопку Clear all problem reports.

Для быстрого освобождения места на диске от файлов отладки, сгенерированных службой WER, содержимое следующих каталогов можно безболезненно удалить и руками.

Windows Error Reporting Service

Windows Error Reporting is a separate Windows service that can be easily disabled using this command:

net stop WerSvc

In the WERReportQueue directory there are a lot of folders with the names in the following format:

  • Critical_6.3.9600.11285__00000000_cab_3212dd23
  • Critical_powershell.exe__cab_332a45c5
  • Critical_sqlservr.exe___cab_b3a200181
  • NonCritical_7.9.9600.11285___0bfab19a
  • AppCrash_cmd.exe__dba332ad_12eb5425

As you can see, the name of the directory contains the severity level of an event and the name of the specific EXE file that has crashed. In all folders, there is a file called Report.wer, which contains the description of the errors and some files with the additional information.

Отключаем сбор и отправки отчетов об ошибках в Windows 10

HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

Можно отключить сбор ошибок WER для конкретных пользователей:

reg add «HKCUSoftwareMicrosoftWindowsWindows Error Reporting» /v «Disabled» /t REG_DWORD /d «1» /f

Или отключить WER для всех: reg add «HKLMSoftwareMicrosoftWindowsWindows Error Reporting» /v «Disabled» /t REG_DWORD /d «1» /f

Измените параметр реестра и проверьте статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

Rempl Папка

Расположение: C: Program Files rempl

В то время как Rempl папка небольшая, вы можете удивиться, увидев ее в своей системе. Он содержит несколько небольших файлов, и вы можете даже заметить несколько процессов диспетчера задач, связанных с ним.

Эта папка подключена к доставке обновлений Windows 10. Это включает » Повышение надежности », чтобы обновления Windows 10 проходили гладко и устраняли проблемы совместимости.

Так вы можете удалить папку Rempl? Там, по-видимому, нет никаких негативных последствий от этого. Тем не менее, поскольку он занимает всего несколько мегабайт и может сделать обновление Windows менее сложным, лучше его оставить.

Что это за папка inetpub windows 10

Папка inetput появляется на локальном системном диске только после активации такого компонента Windows, как Службы Internet Information Services (IIS). Службы IIS поддерживают веб- и FTP- серверы, а также веб-сайты ASP.NET, динамическое содержимое, такое как классические ASP и CGI, и локальное и дистанционное управление. Проще говоря текущая директория представляет собой специальный набор серверов интернета, созданный корпорацией Microsoft. А в папке inetpub сервис хранит данные об имеющихся серверах и их настройках.

При открытии каталога inetput пользователь обнаруживает папки: custerr, ftproot, history, logs, temp, wwwroot. Хоть и сама папка занимает не так уж и много памяти на системном диске, но сама служба может потреблять не мало оперативной памяти. А также рекомендуем ознакомиться с инструкцией как очистить диск С от ненужных файлов в Windows 10.

Как найти папочку appdata?

Теперь нужно рассмотреть местоположение папочки, как можно в неё войти и суть её структуры, чтобы нам стало ясно, что вы можете в ней деинсталлировать, а что нет.

Разумеется, сначала нам нужно произвести включение скрытых папочек. Чтобы этого добиться:

Войдём в «Панель управления», используя «Пуск»

Для Виндовс 10 в строку запроса вводим «Панель…» и переходим по найденному файлу;
Справа вверху включаем «Крупные значки», затем ищем среди папочек «Параметры проводника»;
В новом окошке обращаем внимание на меню «Вид». Прокручиваем бегунок в самый низ и ставим чикбокс над командой «Показывать скрытые файлы…»

Жмём «Применить», Ок.

Reportqueue что это за папка

Свободное пространство диска С в процессе эксплуатации Windows постоянно сокращается. В независимости от действий пользователя — хочет он того или нет.

Временные файлы, архивы, куки и кеш браузеров и прочие программные элементы, выполнив свою единоразовую миссию (обновление, установка, распаковка), оседают в папках раздела С.

Плюс к этому — полезные мегабайты и гигабайты поглощают некоторые функциональные модули Windows.

Первое средство от такого «захламления» — комплексная очистка диска С. Выполнять её нужно регулярно и своевременно. В противном случае, раздел переполнится, и вы не сможете полноценно пользоваться ОС и, соответственно, ПК.

Windows будет постоянно прерывать вашу работу предупреждающими сообщениями — «недостаточно памяти». Станет невозможен просмотр видео онлайн, так как браузер не сможет сохранять на компьютере закачиваемый с сервера контент.

Могут произойти и другие неприятности.

Служба Windows Error Reporting

Служба Windows Error Reporting представляет собой отдельный сервис Windows, который можно легко отключить командой:

net stop WerSvc

Внутри каталога WERReportQueue содержится множество каталогов, с именами в формате:

  • Critical_6.3.9600.18384_{ID}_00000000_cab_3222bf78
  • Critical_powershell.exe_{ID}_cab_271e13c0
  • Critical_sqlservr.exe__{ID}_cab_b3a19651
  • NonCritical_7.9.9600.18235__{ID}_0bfcb07a
  • AppCrash_cmd.exe_{ID}_bda769bf_37d3b403

Как вы видите, имя каталога содержит степень критичности события и имя конкретного exe файла, который завершился аварийно. Во всех каталогах обязательно имеется файл Report.wer, который содержит описание ошибок и несколько файлов с дополнительной информацией.

Отключение функции сбора и отправки отчетов в windows 10

В Windows 10 возможность отключить Error Reporting через GUI отсутствует. Проверить статус компонента можно в панели управления Система и безопасность ->Центр безопасности и обслуживания -> секция Обслуживание.

Отключить Windows Error Reporting в Windows 10 можно через реестр. Для этого в ветке HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting нужно создать новый параметр типа DWORD (32 бита) с именем Disabled и значением 1.

Теперь еще раз проверим статус параметра Поиск решения для указанных в отчетах проблем в панели управления. Его статус должен изменится на Отключено.

Отключение Error Reporting в Windows 10

В Windows 10 опция «Параметры отчета о неполадках» была удалена из окна параметров центра поддержки, поэтому для отключения формирования отчетов о программных ошибках в этой версии системы придется действовать в обход.

Откройте через окошко «Выполнить» одноименной командой редактор реестра Regedit и раскройте ключ:

HKLMSOFTWAREMicrosoftWindowsWindows Error Reporting

Назовите его Disabled и задайте в качестве его значения единицу.

Сохраните настройки, закройте редактор реестра и перезагрузите компьютер.

Описание примера отключения функции Error Reporting через редактор групповых политик мы опускаем, поскольку его результат является эквивалентным применяемому твику реестра, к тому же редактор gpedit. msc доступен не всех редакциях Windows.

Ошибка Wermgr

Для устранения ошибки Wermgr почти все операции будут идентичны.

Проверка на вирусы

Для всех антивирусов процедура проверки идентична:

  1. Открыть антивирусную программу.
  2. Запустить полное сканирование.
  3. Если антивирус выявил наличие троянских червей или других подозрительных утилит, занести их в карантин и удалить.
  4. Перезагрузить ПК.

Очистка от мусора

Снова нужно использовать CCleaner:

  1. Запустить утилиту.
  2. Кликнуть по кнопке «Анализ».
  3. Кликнуть «Очистка» в правом нижнем углу по завершении анализа.
  4. Нажать кнопку «Продолжить».
  5. Перезагрузить ПК.

Проверка драйверов

Иногда подобный сбои дают устаревшие или сбойные драйвера, для их проверки следует:

  1. Зажать клавиши «Win» + «Pause/Break».
  2. Выбрать вкладку «Диспетчер устройств».
  3. Откроется окно, в котором проблемные драйвера будут помечены желтым восклицательным знаком.
  4. Удалить и заново загрузить их.
  5. Перезагрузить устройство.

Обновить ОС

Действия совершаемые пользователем по решению проблемы, будут идентичны с пунктом по обновлению системы в исправлении ошибки «WerFault».

Проверка системы

В этом случае пользователю необходимо будет снова воспользоваться командной строкой и утилитой «Scannow». Как это сделать пользователь найдет в пункте про решение проблемы с WerFault.

Восстановление системы

Одним из последних вариантов является восстановление ОС:

  1. Кликнуть меню «Пуск» и зайти в поиск.
  2. Прописать «Восстановление».
  3. Откроется окно, в котором выбрать «Настройка восстановления системы».
  4. Выбрать системный диск «С».
  5. Кликнуть кнопку «Настроить».
  6. Установить маркер на «Включить…»
  7. Нажать «Ок».
  8. Запустить восстановление.
  9. Проследовать подсказкам мастера по восстановлению.
  10. Подождать пока Windows возвратится к нужной точке и компьютер перезагрузится.

Переустановка

Если не помог ни один из вышеперечисленных способов, то рекомендуется полная переустановка:

  1. Вставить диск с установочным файлом в дисковод.
  2. Перезагрузить компьютер и пока он загружается быстрым нажатием кнопки «Delete» войти в БИОС.
  3. Выставить первым загрузочным устройством установочный диск.
  4. Сохранить и выйти.
  5. Далее следовать подсказкам мастера установки Windows.

Система отчета об ошибках Windows Error Reporting (WER) является сложным механизмом, автоматизирующим представление сбоев процессов пользовательского режима и режима системных сбоев.

Windows Error Reporting может быть настроена путем перехода в Панель управления (Control Panel) и выбора Центр поддержки (Action Center) -> Настройка центра поддержки (Change Action Center) -> Параметры отчета о неполадках (Problem Reporting Settings).

Когда фильтр необработанных исключений, упомянутый в предыдущем разделе, отлавливает такое исключение, он создает контекстную информацию (например, текущее значение регистров и стека) и открывает подключение ALPC-порта к WER-службе. Эта служба приступает к анализу состояния аварийной программы и выполняет соответствующие действия по уведомлению пользователя. Во многих случаях это означает запуск программы WerFault.exe, которая выполняется с полномочиями текущего пользователя, и пока система не настроена на обратное, выводит окно сообщения, информирующее пользователя об аварии. На системах с установленным отладчиком показаны дополнительные возможности по отладке показанного процесса, что можно увидеть на рисунке.

При щелчке на пункте отладки (Debug) будет запущен отладчик (зарегистрированный в строке Debugger, рассмотренном ранее параметре AeDebug), чтобы подключиться к аварийному процессу.

Windows-Error-Reporting

Диалоговое окно Windows Error Reporting.

На системах с исходными настройками отчет об ошибке (мини-дамп и XML-файл с различными подробностями, например, с номерами версий DLL-библиотек, загруженных в процессе) отправляется на интернет-сервер Microsoft, занимающийся анализом аварийных ситуаций. Затем, когда служба уведомляется о решении для проблемы, она выводит подсказку, информирующую пользователя о его действиях, которые нужно выполнить для решения проблемы.

Входящее сообщение будет также отображено в Центре поддержки. Кроме того, в Мониторе стабильности системы (ReliabilityMonitor) будут также показаны все экземпляры аварий приложений и системы.

ПРИМЕЧАНИЕ. WER будет активно (визуально) информировать пользователя аварийного приложения только в том случае, когда приложение имеет как минимум одно видимое интерактивное окно. В противном случае авария будет занесена в журнал, но пользователю придется вручную зайти в Центр поддержки для просмотра соответствующей записи. Такое поведение призвано избавить пользователя от путаницы, не выводя диалогового окна WER, относящегося к невидимым аварийным процессам, о которых пользователь может не знать, например, о службе, выполняемой в фоновом режиме.

В окружениях, где системы не подключены к Интернету или где администратор хочет контролировать, какие именно отчеты об ошибках будут представлены Microsoft, место назначения отчета об ошибках может быть настроено на внутренний файловый сервер. Средство MicrosoftSystemCenterDesktopErrorMonitoringпонимает структуру каталогов, созданных WindowsErrorReporting, и предоставляет администратору возможность получить избирательные отчеты об ошибках и отправить их компании Microsoft.

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

Чтобы избежать подобных проблем, если сбой произошел в самом фильтре необработанных исключений, имеющийся в Windows механизм WER выполняет эту работу вне аварийного потока, что позволяет зарегистрировать любую аварию процесса или потока и уведомить о ней пользователя.

WER содержит множество настраиваемых параметров, к которым пользователь может получить доступ через редактор групповой политики (Group Policy) или внося изменения в реестр вручную. В таблице представлен список вариантов настройки WER в реестре, показано их использование и возможные значения.

Эти значения находятся в подразделе HKLMSOFTWAREMicrosoftWindows Windows Error Reporting для настроек компьютера и в аналогичном пути в разделе HKEY_CURRENT_USER для настройки для каждого пользователя.

Настройки WER в реестре.

Настройка Смысл Значение
ConfigureArchive Содержание архивных данных 1 — для параметров, 2 — для всех данных
ConsentDefaultConsent Какие данные должны требовать согласия 1— для любых данных, 2 — только для параметров, 3 — для параметров и безопасных данных, 4 — для всех данных.
ConsentDefaultOverrideBehavior Должен ли
DefaultConsent замещать значения согласия дополнительного модуля WER
1 — для разрешения замещения
ConsentPluginName Значение согласия для конкретного дополнительного модуля WER То же самое, что и для
DefaultConsent
CorporateWERDirectory Каталог для общего хранилища WER Строка, содержащая путь
CorporateWERPortNumber Порт, используемый для
общего хранилища WER
Номер порта
CorporateWERServer Имя, используемое для общего хранилища WER Строка, содержащая имя
CorporateWERUseAuthentication Использование для
общего хранилища WER встроенной аутентификации Windows (Windows
Integrated Authentication)
1— для разрешения встроенной аутентификации
CorporateWERUseSSL Использование для
общего хранилища WER протокола защищенных сокетов (SSL)
1 — для разрешения SSL
DebugApplications Список приложений, требующих от пользователя выбора между отладкой (Debug) и продолжением (Continue) 1— для предоставления
пользователю возможности
выбора
DisableArchive Включен ли архив 1 — для выключения архива
Disabled Выключена ли служба WER 1 — для выключения WER
DisableQueue Определение необходимости постановки
отчетов в очередь
1 — для выключения очереди
DontShowUI Выключение или включение WER UI 1 — для выключения UI
DontSendAdditionalData Предотвращение отправки дополнительных данных об аварии 1 — не отправлять
ExcludedApplicationsAppName Список приложений, исключенных из WER Строка, содержащая список
приложений
ForceQueue Нужно ли отчеты отправлять в очередь пользователя 1 — для отправки отчетов в очередь
LocalDumpsDumpFolder Путь, который нужно использовать для хранения дапм-файлов Строка, содержащая путь
LocalDumpsDumpCount Максимальное количество дапм-файлов в пути Счетчик
LocalDumpsDumpType Тип дампа, генерируемого при аварии 0 — для специального дампа, 1 — для мини-дампа, 2 — для полного дампа
LocalDumpsCustomDumpFlags Для специальных дампов, указывает их специализацию Значения, определенные в MINIDUMP_TYPE
LoggingDisabled Включение или выключение ведения журнала 1 — для выключения ведения журнала
MaxArchiveCount Максимальный размер архива (в файлах) Значение в диапазоне 1–5000
MaxQueueCount Максимальный размер очереди Значение в диапазоне 1–500
QueuePesterInterval Дни между запросами, чтобы пользователь мог проверить решения Количество дней

ПРИМЕЧАНИЕ. Значения, перечисленные в параметре LocalDumps, могут также быть настроены для каждого приложения путем добавления имени приложения в пути подраздела между LocalDumps и соответствующим значением. Но они не могут быть настроены для каждого пользователя, поскольку существуют только в пути HKLM.

Как уже говорилось, для связи с аварийными процессами служба WER использует ALPC-порт. Этот механизм использует общесистемный порт ошибки, который служба WER регистрирует через функцию NtSetInformationProcess (использующую DbgkRegisterErrorPort). В результате все процессы Windows теперь имеют порт ошибки, который на самом деле является объектом ALPC-порта, зарегистрированным службой WER. Ядро, которое уведомляется об исключении в первую очередь, использует этот порт для отправки сообщения службе WER, которая затем анализирует аварийный процесс.

Это означает, что даже в тяжелых случаях повреждения состояния потока WER все равно сможет получить уведомления и запустить WerFault.exe для вывода пользовательского интерфейса, не перекладывая эту работу на сам аварийный поток. Кроме того, WER сможет сгенерировать аварийный дамп процесса, и в журнал событий (Event Log) будет записано сообщение. Тем самым будут решены все проблемы тихой смерти процесса: пользователи оповещены, отладка может быть произведена, а администраторы службы могут просмотреть аварийное событие.

Понравилась статья? Поделить с друзьями:
  • Windows error reporting service отключить windows 10
  • Windows error reporting service в русской версии
  • Windows error reporting 1001 как исправить
  • Windows find and replace in files
  • Windows error reporting 1001 имя события bluescreen