2

The Strange Case of GetEnvironmentStringsA

 2 years ago
source link: http://www.os2museum.com/wp/the-strange-case-of-getenvironmentstringsa/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The Strange Case of GetEnvironmentStringsA

It was recently pointed out to me that a simple “hello world” style application built with Open Watcom C/C++ 1.9 does not run on Win32s version 1.30, even though the same executable runs just fine on Windows NT 3.51, Windows 95, or Windows 10.

More specifically, the program crashes rather early on Win32s. With the help of map files and source code, I established that the crash occurs in an internal function called __setenvp, which tries to dereference a null pointer stored in an internal variable _RWD_Envptr.

The _RWD_Envptr variable is filled in by the GetEnvironmentStrings API in the C runtime startup code. The GetEnvironmentStrings API call ends up importing GetEnvironmentStringsA from KERNEL32.DLL. And clearly GetEnvironmentStringsA is failing on Win32s, although it works just fine on NT and Win9x.

Further probing revealed that the GetEnvironmentStrings API has curious history. On Windows NT 3.1, there was only GetEnvironmentStrings (no A or W suffix). On all later Win32 implementations, starting with NT 3.5, there’s GetEnvironmentStringsA and GetEnvironmentStringsW, as well as FreeEnvironmentStringsA and FreeEnvironmentStringsW.

On NT 3.1, there was no FreeEnvironmentStrings, presumably because GetEnvironmentStrings returned a pointer to existing memory that couldn’t be freed (and would be freed at process termination anyway). On NT 3.5, GetEnvironmentStringsA converts the strings provided by GetEnvironmentStringsW and allocates memory for the converted strings, so there is something to free.

A quick experiment with Microsoft Visual Studio 4.0 showed that a test application does run on Win32s; reading MSVC 4.0 runtime source code also revealed that Microsoft calls GetEnvironmentStringsA and immediately terminates the process if GetEnvironmentStringsA fails. So… how can that work on Win32s?

Examining the EXE file produced by MSVC 4.0 revealed that it imports GetEnvironmentStrings and not GetEnvironmentStringsA. Changing the Open Watcom kernel32.lib import library to make GetEnvironmentStringsA an alias of GetEnvironmentStrings made the application work on Win32s. But why?

A closer look at W32SCOMB.DLL shipped with Win32s showed the cause of the odd Win32s behavior. Although W32SCOMB.DLL exports all of GetEnvironmentStrings, GetEnvironmentStringsA, and GetEnvironmentStringsW, the latter two are stubs which always fail, and only GetEnvironmentStrings with no suffix actually does something useful. That seems like a bug in Win32s—GetEnvironmentStringsA should have been an alias of GetEnvironmentStrings.

The mess was most likely caused by a design defect in Windows NT 3.1. The plain GetEnvironmentStrings function probably should never have existed, only GetEnvironmentStringsA and GetEnvironmentStringsW, as is the case with other APIs. Windows NT 3.5 corrected the oversight, but its KERNEL32.DLL still had to export the suffix-free GetEnvironmentStrings—otherwise almost all existing applications would have been broken.

Win32s tracked the development of Windows NT, therefore it implemented GetEnvironmentStrings, and initially only that. Win32s version 1.20 (1994) added GetEnvironmentStringsA and GetEnvironmentStringsW, but only as dummies. As mentioned above, making GetEnvironmentStringsA always fail was arguably wrong… but wasn’t noticed because Microsoft’s programs did not use GetEnvironmentStringsA.

At least up to and including MSVCRT40.DLL, Microsoft’s runtime DLLs only imported GetEnvironmentStrings. That also illustrates why any reasonable Win32 implementation needs to provide the GetEnvironmentStrings import and not just GetEnvironmentStringsA; if it didn’t, quite a few older applications would break because they need the suffix-free GetEnvironmentStrings.

Win32 SDK Details

As mentioned above, tweaking the kernel32.lib import library is one way to work around the problem with GetEnvironmentStringsA on Win32s. But that’s not what Microsoft’s SDK does.

Here is how WINBASE.H in the NT 3.5 SDK defined the then-new FreeEnvironmentStrings API:

WINBASEAPI BOOL WINAPI
FreeEnvironmentStringsA(LPSTR);

WINBASEAPI BOOL WINAPI
FreeEnvironmentStringsW(LPWSTR);

#ifdef UNICODE
#define FreeEnvironmentStrings  FreeEnvironmentStringsW
#else
#define FreeEnvironmentStrings  FreeEnvironmentStringsA
#endif // !UNICODE

That’s the usual way of dealing with Unicode APIs. Function prototypes have ‘A’ and ‘W’ suffix, and a suffix-less macro is defined to map to one or the other.

But that’s not how GetEnvironmentStrings was dealt with:

WINBASEAPI LPSTR WINAPI
GetEnvironmentStrings(VOID);

WINBASEAPI LPWSTR WINAPI
GetEnvironmentStringsW(VOID);

#ifdef UNICODE
#define GetEnvironmentStrings  GetEnvironmentStringsW
#else
#define GetEnvironmentStringsA  GetEnvironmentStrings
#endif // !UNICODE

The non-Unicode function prototype has no suffix, and the macro is “backwards”, mapping the ‘A’ function to the suffix-less original. Thus when the Microsoft runtime calls GetEnvironmentStringsA, the compiler ends up generating a call to GetEnvironmentStrings instead. This oddity persists to the present day and even Windows 10 SDK headers handle GetEnvironmentStrings the same way.

Moral of the story? Changing operating system APIs is a messy business.

This entry was posted in Development, NT, Watcom. Bookmark the permalink.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK