The Strange Case of GetEnvironmentStringsA
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK