On Windows 11, the BCrypt library contains the Microsoft cryptographic functions. I have noticed a unique behaviour of the linker when linking against bcrypt.dll and I would appreciate the opinion of specialists.
In short, it seems that building in release mode links against the DLL bcrypt.dll while building in debug mode links agains some static library. This behaviour is unique to bcrypt.dll. I haven't seen that with any other library. Note that the presumed static library does not exist and the core reason is obviously different.
Using the sample code below, we see the difference between two symbols: BCryptEncrypt from bcrypt.dll and CertOpenStore from crypt32.dll. In practice, all symbols from bcrypt.dll have the same behaviour.
Each symbol is first used as a direct reference in the code. Its address is displayed, with the name of the module at that address. Then, the symbol is explicitly loaded, as a string, from the expected DLL and the returned address is displayed.
Release mode:
BCryptEncrypt at 7ff82d9fb200, linked from C:\WINDOWS\SYSTEM32\bcrypt.dll
BCryptEncrypt at 7ff82d9fb200, loaded from bcrypt.dll
CertOpenStore at 7ff82ddec5f0, linked from C:\WINDOWS\System32\CRYPT32.dll
CertOpenStore at 7ff82ddec5f0, loaded from crypt32.dll
In release mode, for the two symbols, the one which was linked from the source code and the one which was explicitly loaded as a string from the DLL are identical.
Debug mode:
BCryptEncrypt at 7ff6ee6813d9, linked from D:\test\win-memory-map\build\Debug-x64\simple-demo.exe
BCryptEncrypt at 7ff82d9fb200, loaded from bcrypt.dll
CertOpenStore at 7ff82ddec5f0, linked from C:\WINDOWS\System32\CRYPT32.dll
CertOpenStore at 7ff82ddec5f0, loaded from crypt32.dll
In debug mode, the symbol BCryptEncrypt behaves differently. The symbol which is linked from the source code is located inside the main executable. In practice, this is the same for all symbols in bcrypt.dll.
So, the obvious idea is that the debug version was linked against a static library version of bcrypt.lib while the release version was linked against the dynamic library. And this is true for bcrypt.dll only, not crypt32.dll, not any other library.
However, there is only one version of bcrypt.lib for x64 on the disk, in C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\um\x64\bcrypt.lib. Using command dumpbin, there is no specific static code inside.
Looking at the declarations in the various system headers, we see that all functions, from all system libraries, have a directive __declspec(dllimport), except all functions from bcrypt.h which do not have this directive.
So, the various aspects of the question are:
__declspec(dllimport)? Specifically, in the case of the release build, the symbols are successfully retrieved from the DLL.bcrypt.lib does not contain any code, just symbols, as with any DLL import library?Sample code to reproduce this, straight to the point for the sake of demo, no error processing:
#include <iostream>
#include <iomanip>
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "bcrypt.lib")
void test(const void* mem_addr, const char* symbol, const char* dll_name)
{
char mem_name[2048] = { 0 };
HMODULE mem_mod = nullptr;
HMODULE dll_mod = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, dll_name, &dll_mod);
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, LPCSTR(mem_addr), &mem_mod);
GetModuleFileNameA(mem_mod, mem_name, DWORD(ARRAYSIZE(mem_name)));
const void* dll_addr = GetProcAddress(dll_mod, symbol);
std::cout << symbol << " at " << std::hex << std::setw(8) << uintptr_t(mem_addr) << ", linked from " << mem_name << std::endl;
std::cout << symbol << " at " << std::hex << std::setw(8) << uintptr_t(dll_addr) << ", loaded from " << dll_name << std::endl;
}
int main(int argc, char* argv[])
{
test(BCryptEncrypt, "BCryptEncrypt", "bcrypt.dll");
test(CertOpenStore, "CertOpenStore", "crypt32.dll");
}
How can the link operation succeed without
__declspec(dllimport)? Specifically, in the case of the release build, the symbols are successfully retrieved from the DLL.
__declspec(dllimport) is just a hint for the compiler to directly call an imported function instead of calling a thunk that then jumps to the actual function. You can read more about it here.
Why does the release version link against the DLL while the debug version links against some static code? There is nothing specific in the VS project file about this library.
It is the abovementioned thunk that resides in the executable that makes it look like you linked against some static code. The default release configuration has LTCG enabled which doesn't rely on the __declspec(dllimport) hint and calls the function directly regardless.
Where does this static code come from, since the import library bcrypt.lib does not contain any code, just symbols, as with any DLL import library?
It's the thunk the compiler generated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With