Обработка нарушений доступа из.NET
Мы запускаем программу как сервис и подключаем к ней adplus для получения аварийных дампов.
При запуске мы периодически получаем аварийные дампы с первым нарушением прав доступа со следующим стеком вызовов
0:011> !mk -cc
Thread 11:
IP
00:M 00007ffa710ca358 PerformanceMonitor.GetData(String)(+0x19 IL,+0x88 Native)
01:M 00007ffa710c7c5f PerformanceCounterLib.GetPerformanceData(String)(+0xff Native)
02:M 00007ffa710c7e2c PerformanceCounterLib.get_CategoryTable()(+0x35 IL,+0xac Native)
03:M 00007ffa710c771e PerformanceCounterLib.GetCategorySample(String, String)(+0xe IL,+0x4e Native)
04:M 00007ffa710b605f PerformanceCounterCategory.GetCounterInstances(String, String)(+0x11 IL,+0x8f Native)
05:M 00007ffa165c4ef1 PerformanceCounterCollection.AddCounter(String, String)(+0xad IL,+0x241 Native)
06:M 00007ffa165c4a9f MonitorResponder.CreatePerformanceCounters()(+0x30 IL,+0x8f Native)
07:M 00007ffa165c47ac MonitorResponder.Start()(+0xa IL,+0x2c Native)
08:M 00007ffa718b39a5 ExecutionContext.RunInternal(ExecutionContext, ContextCallback, Object, Boolean)(+0x72 IL,+0x285 Native)
09:M 00007ffa718b3719 ExecutionContext.Run(ExecutionContext, ContextCallback, Object, Boolean)(+0x0 IL,+0x9 Native)
0a:M 00007ffa718b36f7 ExecutionContext.Run(ExecutionContext, ContextCallback, Object)(+0x57 Native)
0b:M 00007ffa718cadc1 ThreadHelper.ThreadStart()(+0x51 Native)
0c:U 00007ffa75b7a7f3 clr!CallDescrWorkerInternal+0x83
0d:U 00007ffa75b7a6de clr!CallDescrWorkerWithHandler+0x4a
0e:U 00007ffa75b7ae76 clr!MethodDescCallSite::CallTargetWorker+0x251
0f:U 00007ffa75d2969d clr!ThreadNative::KickOffThread_Worker+0x105
10:U 00007ffa75b7c121 clr!ManagedThreadBase_DispatchInner+0x2d
11:U 00007ffa75b7c0a8 clr!ManagedThreadBase_DispatchMiddle+0x6c
12:U 00007ffa75b7c019 clr!ManagedThreadBase_DispatchOuter+0x75
13:U 00007ffa75b7c15f clr!ManagedThreadBase_FullTransitionWithAD+0x2f
14:U 00007ffa75d2957e clr!ThreadNative::KickOffThread+0xd2
15:U 00007ffa75cbfcb6 clr!Thread::intermediateThreadProc+0x7d
16:U 00007ffa7e4a13d2 kernel32!BaseThreadInitThunk+0x22
17:U 00007ffa80b45454 ntdll!RtlUserThreadStart+0x34
Я считаю, что наша свалка на самом деле происходит от:
foreach ( string instanceName in category.GetInstanceNames() )
WinDbg дает этот номер строки, и когда я декомпилировал, он показал, что он вызывает GetCounterInstances.
/// <summary>
/// Retrieves the list of performance object instances that are associated with this category.
/// </summary>
///
/// <returns>
/// An array of strings representing the performance object instance names that are associated with this category or, if the category contains only one performance object instance, a single-entry array that contains an empty string ("").
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The <see cref="P:System.Diagnostics.PerformanceCounterCategory.CategoryName"/> property is null. The property might not have been set. -or-The category does not have an associated instance.</exception><exception cref="T:System.ComponentModel.Win32Exception">A call to an underlying system API failed. </exception><exception cref="T:System.UnauthorizedAccessException">Code that is executing without administrative privileges attempted to read a performance counter.</exception><filterpriority>2</filterpriority><PermissionSet><IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/><IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/><IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/></PermissionSet>
public string[] GetInstanceNames()
{
if (this.categoryName == null)
throw new InvalidOperationException(SR.GetString("CategoryNameNotSet"));
return PerformanceCounterCategory.GetCounterInstances(this.categoryName, this.machineName);
}
Я вижу, что этот метод генерирует InvalidOperationException, Win32Exception, UnauthorizedAccessException.
Наш код на C#, похоже, не имеет обработки исключений в этой области.
Мне интересно: если бы мы попытались перехватить InvalidOperationException, Win32Exception и UnauthorizedAccessException, мы все равно получили бы аварийный дамп с нарушением доступа при первом шансе?
Можно ли обработать нарушение доступа при вызове PerformanceCounterCategory.GetCounterInstances?
Я немного сомневаюсь в том, что нарушения доступа могут быть успешно обработаны. В этом случае мы обращаемся к библиотекам.NET для PerformanceCounters - поэтому мы не можем изменить этот код, чтобы предотвратить нарушение доступа.
Мы не получаем этот сбой очень часто, но достаточно часто, чтобы я узнал стеки вызовов.
Редактировать:
Мы запускаем с legacyCorruptedStateExceptionsPolicy enabled="true"
С нашими QA-серверами - мы работаем с полным дампом и выходим при первом случае нарушения прав доступа.
Я полагаю, что мы рассуждаем по этому поводу о том, что мы не хотим работать с поврежденным процессом, и мы хотим получить как можно больше информации, как только мы получим нарушение прав доступа.
Он может быть вложен глубоко в стек вызовов C++, но у нас может быть управляемый обработчик исключений в точке входа.
Мы не хотим делать полный дамп и продолжать, потому что иногда вы можете попасть в плохое состояние и получить массу аварийных дампов. Кроме того, полный дамп может занять много времени, что, я думаю, может вызвать другие проблемы.
По умолчанию клиенты не работают с подключенным adplus, но если они это делают, они работают с мини-дампом и продолжают при нарушениях прав доступа при первой возможности. Для нас мы всегда работаем с полным дампом при нарушениях прав доступа при первой возможности, потому что мы получаем из них более качественную информацию.
Я предполагаю, что наша дилемма состоит в том, что мы хотим получить полный дамп при первом же случае, если у нас есть нарушение доступа внутри нашего кода C++, но не обязательно, когда мы вызываем код.NET и получаем нарушение доступа, которое мы "обрабатываем". Хотя говорят, что вы не можете "справиться" с нарушениями доступа.
Я видел несколько аварийных дампов на серверах qa для запуска сервера вокруг PerformanceMonitor. Я проверил, и мы действительно ловим исключения вокруг этого. Проблема в том, что когда у нас подключен adplus для выполнения полного дампа и выхода при первом нарушении прав доступа, я получаю эти аварийные дампы.
Полагаю, я могу их игнорировать, поскольку они, вероятно, обрабатываются безопасно, когда у нас не было adplus, настроенного на создание полных дампов и выходов при нарушениях при первом доступе.
0:011> .exr -1
ExceptionAddress: 00007ffa710ca358 (System_ni+0x000000000093a358)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 0000000000000000
Parameter[1]: 0000000000000000
Attempt to read from address 0000000000000000
2 ответа
Вы можете перехватить исключение нарушения прав доступа, пометив свой метод (метод, в котором у вас есть попытка перехватить блок для перехвата рассматриваемого исключения) с помощью HandleProcessCorruptedStateExceptionsAttribute или с помощью конфигурации
<configuration>
<runtime>
<legacyCorruptedStateExceptionsPolicy enabled="true" />
</runtime>
</configuration>
И если вы используете.NET Frameowork 3.5-, они будут перехвачены по умолчанию. Однако, даже если вы можете поймать это, это не значит, что вы можете справиться с этим. Такие исключения называются исключениями из-за поврежденного состояния по причине - состояние вашего процесса может быть повреждено непредсказуемым образом, и поэтому продолжение работы в таком состоянии может привести к непредсказуемым результатам. Таким образом, вы можете поймать его, чтобы записать в журнал, и постепенно завершить работу - не продолжайте запускать ваше приложение в этом состоянии.
Итак, чтобы действительно решить вашу проблему, вы должны найти причину исключения нарушения прав доступа и избавиться от него, а не "обрабатывать" его в блоке catch.
Нарушение прав первого шанса не обязательно означает поврежденное состояние.
Исключение из первого шанса - это просто первый шанс. В исключениях Windows SEH функция фильтра SEH имеет возможность исправить проблему и возобновить работу с ошибочной инструкцией. Только в случае сбоя происходит реальное исключение и __catch
обработчик выполнен.
(В сторону: аналогия для SEH будет SEGV
обработчик в Linux/ Unix. __try
сопоставляется с setjmp, исключение сопоставляется с обработчиком. В обработчике вы можете попытаться решить основную проблему и продолжить, или вызвать longjmp
который по этой аналогии передаст управление условному __catch
блок)
Исключения из первого шанса - обычное средство в окнах, например, при загрузке функций отложенной загрузки. Стандартный путь кода просто устанавливает обработчик, а затем переходит к адресу функции, который изначально равен нулю. Нарушение доступа вызывает обработчик SEH, который загружает таблицу импорта с адресом функции, а затем повторяет вызов.
Если нет необработанных нарушений доступа, вам, вероятно, не нужно беспокоиться об этом. (Исключение - если у вас есть проблемы со стабильностью или вы подозреваете, что такие исключения обрабатываются неправильно).