Обработка нарушений доступа из.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);
}

Отсюда: https://msdn.microsoft.com/en-us/library/vstudio/system.diagnostics.performancecountercategory.getinstancenames(v=vs.100).aspx

Я вижу, что этот метод генерирует 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, который загружает таблицу импорта с адресом функции, а затем повторяет вызов.

Если нет необработанных нарушений доступа, вам, вероятно, не нужно беспокоиться об этом. (Исключение - если у вас есть проблемы со стабильностью или вы подозреваете, что такие исключения обрабатываются неправильно).

Другие вопросы по тегам