Gestion des “Corrupted State Exceptions” par le CLR

Toutes les exceptions n’ont pas une importance égale lors de l’exécution d’un processus. Certaines exceptions peuvent être simplement gérer au niveau d’une fonction ou d’une classe lorsqu’elles surviennent. D’autres exceptions sont plus graves parce qu’elles surviennent, par exemple, lorsque le processus a tenté de corrompre la mémoire du système d’exploitation.

Ainsi le CLR traite les exceptions lancées par le système d’exploitation de façon différente des exceptions survenues dans le processus.

Types d’exception

Les exceptions sont utilisées pour signaler qu’une opération non prévue est survenue lors de l’exécution d’une instruction. Elles peuvent être lancées par du code utilisateur, par du code du framework ou par des instructions de l’API Windows.

Exceptions survenues dans le processus

Les exceptions les plus classiques sont lancées directement par le code avec, par exemple, un throw new NotImplementedException().

Elles peuvent aussi survenir lors d’un appel à une fonction du framework. Par exemple une exception ArgumentException est lancée, si on appelle la fonction File.Open() avec un argument nul.

Généralement, ces exceptions doivent être traitées dans le scope des fonctions exécutant les instructions qui peuvent éventuellement les provoquées. On peut prévenir du lancement de ces exceptions en utilisant try...catch:

try
{  
  FileStream fs = new FileStream(name, FileMode.Create);  
}  
   catch (ArgumentException e) 
{  
  throw new System.IO.IOException("File Open Error!");  
}

D’une façon générale:

  • Lorsqu’une exception est attrapée dans un catch, il faut la traiter en la signalant avec un message de log par exemple. Si une exception n’est pas signalée, elle reste inaperçue et le comportement de la fonction et plus généralement du processus peut être différent du comportement voulu.
  • Il faut traiter des exceptions précises plutôt que de traiter une exception générale du type Exception. Par exemple, il vaut mieux utiliser catch (ArgumentException) plutôt que catch (Exception).
  • Il faut avoir en tête que le traitement des exceptions est couteux pour le CLR et les performances du processus seront affectées si beaucoup d’exceptions surviennent.

Traitement des exceptions survenues dans le processus par le CLR

Les exceptions survenues dans le processus sont traitées par le CLR suivant un système à double-passage (“two-pass exception system”).

Lorsque l’exception survient, le CLR cherche dans la pile des appels précédant l’appel de la fonction où l’exception est survenue, du code qui permet de gérer l’exception. Ainsi, il cherche le “catch” qui va permettre de gérer l’exception. Le premier “catch” trouvé sera exécuté.

Ensuite, il parcourt le code pour trouver et exécuter d’éventuelles clauses finally.

Dans le cas où il n’y a pas de code permettant de gérer une exception, elle sera lancée au niveau du thread, domaine d’applications puis plus généralement au niveau du processus.

Exceptions survenues à l’extérieur du processus

Certaines exceptions sont provoquées par le système d’exploitation parce qu’il tente de se protéger à la suite d’une exécution par le processus d’une instruction qui peut éventuellement le déstabiliser ou le corrompre.

Ces exceptions sont provoquées à l’extérieur du processus parce qu’elles sont provoquées par le système d’exploitation et que ce dernier ne connaît pas l’instruction qui a provoquée l’exception. Il sait juste le processus qui l’a provoqué.

Exceptions Win32 SEH

Windows notifie les exceptions au processus en utilisant l’API Win32 par l’intermédiaire d’une exception SEH (pour “System Exception Handling”). Le CLR convertit ensuite ces exceptions et comme il connaît les instructions qu’il exécute, il peut retrouver l’instruction qui l’a provoquée. Il présente cette exception au code managé après l’avoir encapsulé dans un objet de type Exception.

Toutes les exceptions SEH ne se valent pas en gravité. Par exemple, une exception provoquée par une division par zéro (i.e. DivideByZeroException) n’a pas la même gravité d’une exception provoquée par l’accès à une zone mémoire en dehors de celle du processus (i.e. AccessViolationException).

Ainsi certaines exceptions SEH mettent le processus dans un état catastrophique. Il est, ensuite, préférable d’arrêter le processus plutôt que de le laisser s’exécuter.

Jusqu’au framework .NET 3.5, toutes les exceptions SEH pouvaient être gérées dans une clause “catch”. A partir du framework .NET 4.0, certaines exceptions SEH ne sont pas gérées dans une simple clause “catch”.

Exceptions “Corrupted State Exception” (CSE)

A partir du framework .NET 4.0, certaines exceptions SEH sont considérées assez graves pour nécessiter l’arrêt du processus. A partir du framework 4.0, ces exceptions ne sont plus gérées, par défaut, dans une simple clause “catch”.

Par défaut, lorsque des exceptions de ce type surviennent, le CLR va stopper le processus directement sans essayer de chercher une clause “catch”.

Les exceptions SEH de ce type sont appelées “Corrupted State Exception” (CSE) parce qu’elles mettent le processus dans un état corrompu. Les exceptions SEH de Win32 qui sont considérées comme des CSE sont:

  • EXCEPTION_ACCESS_VIOLATION
  • EXCEPTION_STACK_OVERFLOW
  • EXCEPTION_ILLEGAL_INSTRUCTION
  • EXCEPTION_IN_PAGE_ERROR
  • EXCEPTION_INVALID_DISPOSITION
  • EXCEPTION_NONCONTINUABLE_EXCEPTION
  • EXCEPTION_PRIV_INSTRUCTION
  • STATUS_UNWIND_CONSOLIDATE

Même si ces exceptions ne sont plus traitées, par défaut, dans une clause “catch” comme pour les autres exceptions, il existe des méthodes pour que le code managé puisse les traiter.

Le CLR convertit la plupart de ces exceptions CSE en exception de type System.Runtime.InteropServices.SEH. Toutefois, les exceptions EXCEPTION_ACCESS_VIOLATION sont convertis en System.AccessViolationException. De même, les exceptions EXCEPTION_STACK_OVERFLOW sont convertis en exceptions de type System.StackOverflowException.

Traitement des exceptions CSE à partir du framework 4.0

Traitement des CSE par code

Pour traiter les exceptions CSE (“Corrupted State Exception”) dans du code managé, il faut utiliser l’attribut System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute.

Ainsi quand une exception CSE survient, le CLR va chercher du code managé permettant de gérer cette exception seulement dans les fonctions possédant l’attribut HandleProcessCorruptedStateExceptions.

Même s’il est possible de traiter les exceptions CSE avec du code, il faut garder à l’esprit que si une exception survient, le processus est dans un état instable et qu’il faut qu’il s’arrête. La seule chose qu’on doit faire dans le corps de la clause “catch” est de logger une erreur.

Gestion des SEH:

Toutes les exceptions SEH graves ne sont pas gérées de la même façon:

  • Les exceptions StackOverflowException ne sont pas considérées comme des CSE à partir du framework .NET 2.0 ne peuvent pas être traitées dans du code même avec la présence de l’attribut HandleProcessCorruptedStateExceptions.
  • Les exceptions OutOfMemoryException ne nécessite pas la présence de l’attribut pour être gérées dans du code, une clause catch (OutOfMemoryException) suffit.

Par exemple, pour utiliser l’attribut HandleProcessCorruptedStateExceptions:

using System.Runtime.ExceptionServices;  
using System.Runtime.InteropServices; 
     
[HandleProcessCorruptedStateExceptions] 
public static void HandleCorruptedState() 
{ 
  try 
  { 
    IntPtr ptr = new IntPtr(1000); 
    Marshal.StructureToPtr(1000, ptr, true); 
  } 
  catch (AccessViolationException e) 
  { 
    System.Console.WriteLine(e.Message);  
  } 
} 

Traitement des CSE dans le fichier de configuration

Pour assurer la compatibilité ascendante entre le framework 3.5 et 4.0 dans la gestion des exceptions CSE, on peut utiliser l’élément de configuration legacyCorruptedStateExceptionsPolicy dans le fichier de configuration de l’application. Cet élément permet d’indiquer que les exceptions CSE seront gérées par le code de la même façon que les exceptions normales comme sur les frameworks antérieurs au 4.0.

On peut l’utiliser en le rajoutant dans le fichier de configuration:

<configuration> 
  <runtime> 
    <legacyCorruptedStateExceptionsPolicy enabled="true"/> 
  </runtime> 
</configuration> 
HandleProcessCorruptedStateExceptions est plus fin que legacyCorruptedStateExceptionsPolicy

L’utilisation de legacyCorruptedStateExceptionsPolicy doit être limitée au cas de compatibilité ascendante d’une application du framework 3.5 au framework 4.0. Dans les autres cas, il est préférable d’utiliser l’attribut HandleProcessCorruptedStateExceptions.
En effet, l’attribut HandleProcessCorruptedStateExceptions permet une gestion beaucoup plus fine des exceptions CSE par fonction plutôt qu’une gestion globale pour toute l’application avec l’élément de configuration legacyCorruptedStateExceptionsPolicy.

Leave a Reply