Kerberos libraries on Windows, use of -entry:DllMain
Ken Hornstein via krbdev <[email protected]> Tue, 03 Dec 2024 13:40:07 -0500
| Newsgroups | gmane.comp.encryption.kerberos.devel |
|---|---|
| Message-ID | <[email protected]> |
As discussed in other messages, I have been working on getting the PKINIT plugin built on Windows (we do this for our private distribution of Kerberos for Windows, but this has been a huge maintenance headache for a ton of reasons). As discussed in those previous messages, the sticking point seems to be a regular expression library that is available on Windows and has an acceptable license for MIT. The solution I have been working on is the use of C wrapper functions for the C++ std::regex classes (which are available on Windows). I have what I consider a relatively straightforward implementation of this and I tested it on MacOS X to verify basic functionality and it works fine. However, getting it working in the Windows build environment is straining the limits of my Windows knowledge and I'd like to solicit some other advice. As a note, I am using Visual Studio 2022 for the build and I am aware that the documentation says that VS 2017 is the only thing that is supported, but we have been using newer versions of VS for a while and it hasn't been an issue. Specifically, when I add my k5-regex.obj file to the list of k5sprt32.dll objects (it's the same for a 32 or 64 bit build), when it gets to the point where it tries to build the DLL I get the following errors: MSVCRTD.lib(utility.obj) : error LNK2019: unresolved external symbol __is_c_termination_complete referenced in function ___scrt_dllmain_uninitialize_c MSVCRTD.lib(utility.obj) : error LNK2019: unresolved external symbol ___acrt_initialize referenced in function ___scrt_initialize_crt MSVCRTD.lib(utility.obj) : error LNK2019: unresolved external symbol ___acrt_uninitialize referenced in function ___scrt_uninitialize_crt (there are 11 in total, but the full list isn't relevant, but I can post it here if it would be helpful). So the obvious answer to this would be, "Well, Ken, you need to add whatever DLL defines those symbols to the build!". Alas, if it was only that simple. First, I thought that maybe my changes messed up the build process in other ways, but I verified that it was the inclusion of calls to C++ classes that caused these undefined symbol errors to occur. If I commented out the calls to those classes in my k5-regex.cpp code, the DLL would build fine. There isn't any other C++ code in MIT Kerberos DLLs so it's not surprising this didn't come up before. Okay, fine, so maybe I needed to add another system DLL? Well, no, turns out that isn't the issue, or at least it's not that symbol. Some Googling turned up this page: https://learn.microsoft.com/en-us/archive/msdn-technet-forums/dadf3d7e-2f3d-4b6e-b703-1012dc2b63a7 While the ultimate suggestion for that problem was that they needed to update their build process to include some missing DLLs, the list of DLLs that post referenced were already included by the MIT build process. However, it did provide a clue that these symbols are a little bit magical in that they are defined in mscvrt.lib using the /alternatename linker directive: % dumpbin /directives msvcrt.lib [...] Linker Directives ----------------- /merge:.CRT=.rdata /alternatename:__is_c_termination_complete=___scrt_stub_for_is_c_termination_complete /alternatename:___vcrt_initialize=___scrt_stub_for_acrt_initialize /alternatename:___vcrt_uninitialize=___scrt_stub_for_acrt_uninitialize [...] These symbols aren't defined anywhere as "real" symbols as far as I can tell, so they have to be resolved via the alteratename directive. Now why aren't they? Well, good question! I'm a little bit unclear on that! Taking a step back, I decided to see if I could build my own DLL with just the k5-regex glue functions I wrote ... and that worked just fine. So I decided to try to figure out what was different about the MIT build process that caused this to fail. Skip forward to some brute-force testing of removing different build options, and it turns out the core issue was the use of "-entry:DllMain" switch on the MIT Kerberos DLL build line. If I remove THAT, the DLLs build just fine. So, I dug into that more. It turns out that the use of the -entry switch is no longer recommended. Specifically, from here: https://learn.microsoft.com/en-us/cpp/build/run-time-library-behavior?view=msvc-170 Older Windows SDK documentation says that the actual name of the DLL entry-point function must be specified on the linker command-line with the /ENTRY option. With Visual Studio, you do not need to use the /ENTRY option if the name of your entry-point function is DllMain. In fact, if you use the /ENTRY option and name your entry-point function something other than DllMain, the CRT does not get initialized properly unless your entry-point function makes the same initialization calls that _DllMainCRTStartup makes. The MIT Kerberos DLLs do use DllMain as the name of their DLL initializer function, so according to the documentation the default DLL initializer function will call the MIT initializer. And I am pretty sure that the MIT DllMain does not call all of the same initialization calls that _DllMainCRTStartup does. Digging through the code shows me that the -entry:DllMain switch appeared sometime in 1999(!). And it went in with the following comment: # XXX - NOTE: We should probably use DllMainCRTStartup I see there were actually fixes for this for the ccapi.dll where it was switched to use _DllMainCRTStartup, which I suppose isn't surprising since that code has C++ code in it. But this begs the question: should we even include that flag at all? It seems like the most robust option is to simply use the system default and rely on the documented behavior that it will call the provided DllMain function. I did try a build where I removed the -entry:DllMain for everything but kinit seemed to simply exit without returning an error when I tried running it (a previous build without my code worked fine). It looked like it failed when trying to load the service_locator plugin, so I need to dig into this more. But I wanted to get some feedback at this point: is there a reason to specify a DLL entry point anymore? I am reaching the limit of my Windows development knowledge so any advice is welcome. If anyone wants to look at this code, it's available at: https://github.com/kenh/krb5.git Under the "pkinit-windows" branch. --Ken _______________________________________________ krbdev mailing list [email protected] https://mailman.mit.edu/mailman/listinfo/krbdev