[20514] in Kerberos_V5_Development
Kerberos libraries on Windows, use of -entry:DllMain
daemon@ATHENA.MIT.EDU (Ken Hornstein via krbdev)
Tue Dec 3 13:40:23 2024
Message-Id: <202412031840.4B3Ie88Y032384@hedwig.cmf.nrl.navy.mil>
To: krbdev@mit.edu
MIME-Version: 1.0
Date: Tue, 03 Dec 2024 13:40:07 -0500
From: Ken Hornstein via krbdev <krbdev@mit.edu>
Reply-To: Ken Hornstein <kenh@cmf.nrl.navy.mil>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: krbdev-bounces@mit.edu
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 krbdev@mit.edu
https://mailman.mit.edu/mailman/listinfo/krbdev