Les fonctions du noyau Windows et les drivers utilisent très fréquemment le type NTSTATUS pour indiquer le résultat d'une opération.
Un NTSTATUS ne représente pas uniquement une erreur. Il peut représenter :
- une opération réussie ;
- une information ;
- un avertissement ;
- une erreur.
Code: Select all
NTSTATUS status = IoCreateDevice(...);
if (!NT_SUCCESS(status))
{
KdPrint(("IoCreateDevice failed: 0x%08X\n", status));
return status;
}
1. Le type NTSTATUS
Un NTSTATUS est une valeur codée sur 32 bits.
Une fonction kernel peut par exemple être déclarée ainsi :
Code: Select all
NTSTATUS MyFunction();
Code: Select all
NTSTATUS status = MyFunction();
if (!NT_SUCCESS(status))
{
return status;
}
2. Structure d'un NTSTATUS
Un NTSTATUS est composé de plusieurs champs répartis dans ses 32 bits.
La structure générale est :
Code: Select all
31 30 29 28 16 15 0
+-----+--+--+--------------------+------------------------+
| Sev | C| R| Facility | Code |
+-----+--+--+--------------------+------------------------+
- Severity : indique la gravité du résultat ;
- C (Customer) : indique notamment si le code appartient à un ensemble de codes personnalisés ;
- R : bit réservé ;
- Facility : indique la catégorie ou le composant ayant produit le statut ;
- Code : identifie la condition précise.
3. Severity
Les deux bits de poids fort déterminent la sévérité du NTSTATUS.
Il existe quatre catégories :
- 00 : Success ;
- 01 : Informational ;
- 10 : Warning ;
- 11 : Error.
Code: Select all
0x00000000 ... -> Success
0x40000000 ... -> Informational
0x80000000 ... -> Warning
0xC0000000 ... -> Error
4. STATUS_SUCCESS
Le statut le plus simple est :
Code: Select all
STATUS_SUCCESS
Code: Select all
0x00000000
Exemple :
Code: Select all
NTSTATUS status = STATUS_SUCCESS;
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
5. Ne pas tester uniquement STATUS_SUCCESS
Une erreur fréquente consiste à écrire :
Code: Select all
if (status == STATUS_SUCCESS)
{
// succès
}
Or plusieurs NTSTATUS peuvent représenter des résultats qui ne doivent pas être traités comme des erreurs fatales.
Il faut généralement utiliser :
Code: Select all
if (NT_SUCCESS(status))
{
// résultat considéré comme réussi
}
Code: Select all
if (!NT_SUCCESS(status))
{
// échec ou résultat nécessitant un traitement particulier
}
6. Les macros NT_SUCCESS, NT_INFORMATION, NT_WARNING et NT_ERROR
Windows fournit plusieurs macros permettant d'analyser la catégorie d'un NTSTATUS.
La plus utilisée est :
Code: Select all
NT_SUCCESS(status)
On rencontre également :
Code: Select all
NT_INFORMATION(status)
NT_WARNING(status)
NT_ERROR(status)
Exemple :
Code: Select all
if (NT_ERROR(status))
{
KdPrint(("Erreur : 0x%08X\n", status));
}
7. Codes NTSTATUS courants
De nombreux codes sont utilisés régulièrement dans les drivers.
Quelques exemples importants :
- : opération réussie ;
Code: Select all
STATUS_SUCCESS - : opération en attente ;
Code: Select all
STATUS_PENDING - : paramètre invalide ;
Code: Select all
STATUS_INVALID_PARAMETER - : requête non supportée par le périphérique ;
Code: Select all
STATUS_INVALID_DEVICE_REQUEST - : buffer trop petit ;
Code: Select all
STATUS_BUFFER_TOO_SMALL - : le buffer ne permet pas de retourner toutes les données ;
Code: Select all
STATUS_BUFFER_OVERFLOW - : ressources insuffisantes ;
Code: Select all
STATUS_INSUFFICIENT_RESOURCES - : accès refusé ;
Code: Select all
STATUS_ACCESS_DENIED - : opération non supportée ;
Code: Select all
STATUS_NOT_SUPPORTED - : objet demandé introuvable ;
Code: Select all
STATUS_OBJECT_NAME_NOT_FOUND - : périphérique non prêt.
Code: Select all
STATUS_DEVICE_NOT_READY
Code: Select all
if (bufferSize < sizeof(MY_DATA))
{
return STATUS_BUFFER_TOO_SMALL;
}
8. STATUS_PENDING
STATUS_PENDING est particulièrement important dans le modèle d'I/O Windows.
Il signifie qu'une opération n'est pas encore terminée et qu'elle sera complétée ultérieurement.
Exemple conceptuel :
Code: Select all
IoMarkIrpPending(Irp);
return STATUS_PENDING;
Le driver ne doit alors pas traiter STATUS_PENDING comme une erreur classique.
La requête sera complétée plus tard, par exemple avec :
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = bytesTransferred;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
9. NTSTATUS et IRP
Les NTSTATUS sont très importants dans le traitement des IRP.
Une routine de dispatch possède généralement cette forme :
Code: Select all
NTSTATUS DispatchDeviceControl(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
NTSTATUS status = STATUS_SUCCESS;
// traitement...
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
Code: Select all
Irp->IoStatus.Status
Et :
Code: Select all
Irp->IoStatus.Information
10. IoStatus.Information
Une erreur classique consiste à ne gérer que :
Code: Select all
Irp->IoStatus.Status
Code: Select all
Irp->IoStatus.Information
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(MY_DATA);
Code: Select all
Irp->IoStatus.Information = 0;
11. Retourner un NTSTATUS et compléter un IRP
Il faut distinguer deux opérations.
Premièrement :
Code: Select all
return status;
Deuxièmement :
Code: Select all
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
Dans une routine qui possède la responsabilité de compléter l'IRP, on rencontre donc souvent :
Code: Select all
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
12. Warning et Error
La différence entre un avertissement et une erreur peut être importante.
Deux codes qui semblent proches peuvent entraîner des comportements différents.
Par exemple :
Code: Select all
STATUS_BUFFER_OVERFLOW
Alors que :
Code: Select all
STATUS_BUFFER_TOO_SMALL
Cette distinction peut notamment avoir une influence sur le traitement des données retournées par le système d'I/O.
Il ne faut donc pas considérer automatiquement tous les codes non nuls comme identiques.
13. NTSTATUS et erreurs Win32
Les codes NTSTATUS utilisés dans le noyau ne sont pas les mêmes que les codes d'erreur Win32 récupérés avec :
Code: Select all
GetLastError()
Code: Select all
STATUS_INVALID_PARAMETER
Code: Select all
ERROR_INVALID_PARAMETER
Code: Select all
STATUS_SUCCESS
-> ERROR_SUCCESS
STATUS_INVALID_PARAMETER
-> ERROR_INVALID_PARAMETER
STATUS_OBJECT_NAME_NOT_FOUND / certains statuts de fichier
-> erreur Win32 correspondante selon le contexte
STATUS_ACCESS_DENIED
-> ERROR_ACCESS_DENIED
STATUS_INVALID_DEVICE_REQUEST
-> ERROR_INVALID_FUNCTION
14. RtlNtStatusToDosError
Le système fournit une fonction permettant de convertir de nombreux NTSTATUS en codes d'erreur Win32 :
Code: Select all
RtlNtStatusToDosError
Code: Select all
NTSTATUS status = STATUS_ACCESS_DENIED;
ULONG error = RtlNtStatusToDosError(status);
Code: Select all
ERROR_ACCESS_DENIED
Code: Select all
DWORD error = (DWORD)status;
15. NTSTATUS et HRESULT
NTSTATUS et HRESULT sont deux systèmes de codes différents.
NTSTATUS est principalement rencontré dans :
- le noyau Windows ;
- les drivers ;
- les API natives NT ;
- certaines parties internes de Windows.
- COM ;
- certaines API Windows user mode ;
- de nombreuses bibliothèques Microsoft.
16. Lire un NTSTATUS en hexadécimal
Les NTSTATUS sont très souvent affichés sous forme hexadécimale.
Exemple :
Code: Select all
0xC000000D
Code: Select all
11
Code: Select all
Error
Code: Select all
STATUS_INVALID_PARAMETER
Code: Select all
0x00000000
Code: Select all
00
17. Afficher un NTSTATUS pendant le debugging
Pendant le développement d'un driver, il est utile d'afficher directement le code retourné par une fonction.
Exemple :
Code: Select all
NTSTATUS status = IoCreateDevice(...);
if (!NT_SUCCESS(status))
{
KdPrint(("IoCreateDevice failed: 0x%08X\n", status));
return status;
}
Code: Select all
0xC000000D
Dans WinDbg, les commandes de diagnostic des codes d'erreur peuvent également aider à interpréter une valeur rencontrée pendant le debugging.
18. Propager une erreur
Lorsqu'une fonction appelée par un driver retourne une erreur, il est souvent préférable de conserver le NTSTATUS original.
Exemple :
Code: Select all
NTSTATUS status = IoCreateDevice(...);
if (!NT_SUCCESS(status))
{
return status;
}
Écrire systématiquement :
Code: Select all
if (!NT_SUCCESS(status))
{
return STATUS_UNSUCCESSFUL;
}
Si IoCreateDevice retourne par exemple un code précis, il est généralement plus utile de propager ce code.
19. Nettoyage après une erreur
Propager le NTSTATUS ne signifie pas qu'il faut immédiatement retourner sans réfléchir.
Si des ressources ont déjà été créées, elles doivent être libérées.
Exemple :
Code: Select all
status = IoRegisterDeviceInterface(...);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(DeviceObject);
return status;
}
- détecter l'erreur ;
- annuler ou libérer ce qui a déjà été créé ;
- retourner le NTSTATUS approprié.
20. Codes NTSTATUS personnalisés
Le format NTSTATUS prévoit notamment un bit Customer permettant de distinguer certains codes définis par des utilisateurs ou des composants des codes standards du système.
Il est cependant préférable d'utiliser un code NTSTATUS standard lorsqu'il décrit correctement la situation.
Par exemple, pour un paramètre invalide :
Code: Select all
STATUS_INVALID_PARAMETER
Les codes personnalisés sont surtout intéressants lorsqu'une application ou un composant doit représenter une condition réellement spécifique qui n'est pas correctement décrite par les codes existants.
21. Erreurs fréquentes
Plusieurs erreurs sont fréquentes lors de l'utilisation des NTSTATUS.
- Tester uniquement alors que l'on veut tester la réussite selon la convention NTSTATUS.
Code: Select all
status == STATUS_SUCCESS - Considérer toute valeur différente de zéro comme une erreur.
- Confondre un NTSTATUS avec un code retourné par .
Code: Select all
GetLastError() - Confondre NTSTATUS et HRESULT.
- Remplacer systématiquement une erreur précise par .
Code: Select all
STATUS_UNSUCCESSFUL - Oublier de renseigner .
Code: Select all
Irp->IoStatus.Status - Oublier de renseigner correctement .
Code: Select all
Irp->IoStatus.Information - Considérer comme une erreur classique.
Code: Select all
STATUS_PENDING - Créer inutilement un code personnalisé alors qu'un NTSTATUS standard existe déjà.
22. Exemple dans une routine IOCTL
Voici un exemple simple :
Code: Select all
NTSTATUS DeviceControl(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack =
IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
ULONG_PTR information = 0;
switch (stack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_MY_COMMAND:
{
if (stack->Parameters.DeviceIoControl.InputBufferLength
< sizeof(LONG))
{
status = STATUS_BUFFER_TOO_SMALL;
break;
}
LONG* value =
(LONG*)Irp->AssociatedIrp.SystemBuffer;
(*value)++;
status = STATUS_SUCCESS;
information = sizeof(LONG);
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = information;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
- un statut par défaut est choisi ;
- les paramètres sont vérifiés ;
- une erreur précise est utilisée lorsque le buffer est trop petit ;
- STATUS_SUCCESS est utilisé lorsque la commande réussit ;
- IoStatus.Status reçoit le résultat ;
- IoStatus.Information indique la quantité de données retournées ;
- l'IRP est finalement complété.
23. Exemple de gestion d'une fonction kernel
Un schéma extrêmement courant dans un driver est :
Code: Select all
NTSTATUS status;
status = SomeKernelFunction(...);
if (!NT_SUCCESS(status))
{
KdPrint(("SomeKernelFunction failed: 0x%08X\n", status));
return status;
}
// continuer le traitement
- de vérifier immédiatement le résultat ;
- d'afficher le code pendant le debugging ;
- de conserver l'information précise sur l'erreur ;
- d'éviter de continuer avec un état invalide.
24. Résumé
Un NTSTATUS est une valeur 32 bits utilisée massivement par Windows pour représenter le résultat d'une opération.
Il contient notamment :
- une sévérité ;
- une facility ;
- un code identifiant la condition ;
- des bits supplémentaires tels que Customer et Reserved.
Code: Select all
Success
Informational
Warning
Error
Code: Select all
if (!NT_SUCCESS(status))
{
return status;
}
Code: Select all
Irp->IoStatus.Status
Irp->IoStatus.Information
Code: Select all
IoCompleteRequest(Irp, IO_NO_INCREMENT);
- NTSTATUS n'est pas synonyme d'erreur ;
- un statut différent de STATUS_SUCCESS n'est pas automatiquement une erreur ;
- STATUS_PENDING représente une opération qui n'est pas encore terminée ;
- Warning et Error sont deux catégories distinctes ;
- NTSTATUS, erreurs Win32 et HRESULT sont des systèmes différents ;
- une erreur précise doit généralement être propagée plutôt que remplacée par STATUS_UNSUCCESSFUL ;
- les macros NT_SUCCESS, NT_INFORMATION, NT_WARNING et NT_ERROR permettent d'interpréter correctement les différentes catégories de statut.
