Archive for August, 2007

Error Codes: Win32 vs. HRESULT vs. NTSTATUS

There are three common error code formats used throughout Windows. In the kernel and native part, NTSTATUS is used exclusively. The Win32 API uses its own error codes (they do not really have a name, so I will refer to them as Win32 error codes) and COM uses HRESULTs — though the separation is not always so sharp, e.g. the safe string functions (StringCch* and friends) also return HRESULTs although they do not belong to COM.

HRESULT (From winerror.h)

//
//  HRESULTs are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +-+-+-+-+-+---------------------+-------------------------------+
//  |S|R|C|N|r|    Facility         |               Code            |
//  +-+-+-+-+-+---------------------+-------------------------------+
//
//  where
//
//      S - Severity - indicates success/fail
//
//          0 - Success
//          1 - Fail (COERROR)
//
//      R - reserved portion of the facility code, corresponds to NT's
//              second severity bit.
//
//      C - reserved portion of the facility code, corresponds to NT's
//              C field.
//
//      N - reserved portion of the facility code. Used to indicate a
//              mapped NT status value.
//
//      r - reserved portion of the facility code. Reserved for internal
//              use. Used to indicate HRESULT values that are not status
//              values, but are instead message ids for display strings.
//
//      Facility - is the facility code
//
//      Code - is the facility's status code
//

NTSTATUS and Win32 error codes (From Winerror.h or ntstatus.h)

NTSTATUS* and Win32 error codes share the same definition:

//
//  Values are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +---+-+-+-----------------------+-------------------------------+
//  |Sev|C|R|     Facility          |               Code            |
//  +---+-+-+-----------------------+-------------------------------+
//
//  where
//
//      Sev - is the severity code
//
//          00 - Success
//          01 - Informational
//          10 - Warning
//          11 - Error
//
//      C - is the Customer code flag
//
//      R - is a reserved bit
//
//      Facility - is the facility code
//
//      Code - is the facility's status code
//

In user mode, these codes are primarily encountered as SEH exception codes (e.g. EXCEPTION_ACCESS_VIOLATION, 0xC0000005) or return values. However, due to compatibility reasons, all common error codes defined in winerror.h (such as ERROR_FILE_NOT_FOUND, 0x2) do not quite adhere to their definition. Neither have they set Severity to 0y11 nor have they set their facility code to FACILITY_WIN32). Unsurprisingly, they are the same as in OS/2 (see DosExecPgm as an example).

Another unfortunate property of these Win32 error codes is, that no typedef for them exists. In fact, some APIs such as RegOpenKeyEx treat them as signed (LONG), others such as GetLastError treat them as unsigned (DWORD). Again, this is probably due to compatibility reasons.

So how compatible are those codes? Comparing the structure of HRESULTs and NTSTATUS/Win32 error codes, it is worth noting that HRESULTs explicitly allow for holding NTSTATUS values (Informational NTSTATUS become success HRESULTS, Warning NTSTATUS become failure HRESULTs). Even the other way round, assigning HRESULT values to NTSTATUS variables seems to be ok, given that the R, C, N and r bits of HRESULTS are usually 0.

So on a syntactic level, assigning NTSTATUS values to HRESULTs and vice versa seems to be correct. But let us have a look at the facility codes:

NTSTATUS (ntstatus.h):

#define FACILITY_DEBUGGER                0x1
#define FACILITY_RPC_RUNTIME             0x2
#define FACILITY_RPC_STUBS               0x3
#define FACILITY_IO_ERROR_CODE           0x4
[...]

Win32 error codes and HRESULT(winerror.h):

#define FACILITY_RPC                     1
#define FACILITY_DISPATCH                2
#define FACILITY_STORAGE                 3
#define FACILITY_ITF                     4
[...]

Having the same format, NTSTATUS and Win32 error codes could be expected to use the same facility codes. However, this is not the case — instead, Win32 error codes (according to winerror.h) use the facility values of HRESULTs! As a consequence, interchanging NTSTATUS and Win32 error codes is syntacticly ok but changes their semantics due to non matching facility codes.

With this background in mind, it is now possible to define a conversion matrix:

  From
NTSTATUS Win32 HRESULT
To NTSTATUS   Yes 1, 2 Yes 1
Win32 LsaNtStatusToWinError() or HRESULT_FROM_NT() 1, 4   Yes 3
HRESULT HRESULT_FROM_WIN32( LsaNtStatusToWinError()) or HRESULT_FROM_NT() 1, 4 Yes 2  

1 Facility may need to be adapted
2 Holds for ‘real’ Win32 error codes. For compatibility error codes, use HRESULT_FROM_WIN32
3 As long as you have a ‘real’ HRESULT (i.e. not one from HRESULT_FROM_WIN32) and want to get a ‘real’ Win32 error code (i.e. not a compaitibility one) — otherwise it can get tricky
4 Note that HRESULT_FROM_NT does not take the NT Status to Win32 Error Code conversion table into account, thus the result may not be what one would expect. Using LsaNtStatusToWinError takes this table into account, but yields ‘compatibility’ Win32 error code.
* It turns out that the NTSTATUS documentation in the DDK contradicts the definition in ntstatus.h (3790): According to winerror.h, bit 28 is reserved whereas the DDK counts it as part of the facility field (Which, I guess, is wrong).

Install MSI with log from shell context menu

When authoring MSI packages, you frequently need the installation to be logged. Instead of repeatedly opening a console to type in msiexec /l* install.log /i foobar.msi, these shell context menu items may speed this process up a little. They do the same as ‘Install’ and ‘Uninstall’ but log everything (/l*) to PackageName.msi-install.log or PackageName.msi-uninstall.log, respectively.
msi.png

Here are the registry entries:

[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedInstall]
@="&Logged Install"
[HKEY_CLASSES_ROOT\\Msi.Packageshell\\LoggedInstall\\command]
@="msiexec.exe /l* \"%1-install.log\" /i \"%1\" %*"
[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedUninstall]
@="L&ogged Uninstall"
[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedUninstall\\command]
@="msiexec.exe /l* \"%1-uninstall.log\" /x \"%1\" %*"

And in case you have not yet configured ‘Run as’ for MSI files, you may want to add these as well:

[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\runas\\command]
@="msiexec.exe /l* \"%1.log\" /i \"%1\" %*"

MSI shell context menu (Save as and rename to .reg)


Categories




About me

Johannes Passing, M.Sc., living in Berlin, Germany.

Besides his consulting work, Johannes mainly focusses on Win32, COM, and NT kernel mode development, along with Java and .Net. He also is the author of cfix, a C/C++ unit testing framework for Win32 and NT kernel mode, Visual Assert, a Visual Studio Unit Testing-AddIn, and NTrace, a dynamic function boundary tracing toolkit for Windows NT/x86 kernel/user mode code.

Contact Johannes: jpassing (at) acm org

Johannes' GPG fingerprint is BBB1 1769 B82D CD07 D90A 57E8 9FE1 D441 F7A0 1BB1.

LinkedIn Profile
Xing Profile
Github Profile