error LNK2019: unresolved external symbol _ZwDelayExecution@8 referenced in function “int __cdecl foo(void)” (?foo@@YAHXZ)
fatal error LNK1120: 1 unresolved externals
This will show up whenever you try to compile the following example code with “CL” (Microsoft compiler) in the straight normal way. This article is about why, and how.
As Microsoft keep saying, using internal APIs directly in your programs is absolutely not recommended. Because those are subject to change from a service pack to another, or may be a patch to another. But sometimes you just need to use them in some circumstances.
In order to use an internal API, you have two options:
Method 1 – Dynamic Linking:
Which is I guess is pretty well known. Just use LoadLibrary API to load “ntdll”, then use GetProcAddress to get a function pointer to the function you want to use in ntdll. The following example illustrates that.
Method 2 – Importing ntdll.lib:
Which is the main reason I wrote this blog. In this method, you can use the internal APIs directly without a function pointer. They will show up in your executable’s import section normally like any other library you use. If you want to go this way and use this method, what will you do depends on the compiler you’ll use. I’ll cover here GCC (or G++) and CL (Microsoft Compiler).
Option 1 – Using GCC:
MinGW comes loaded with many .lib files, luckily, libntdll.a is one of them. So the process is straight forward. To compile the aforementioned example, just use the following command:
gcc ZwDelayExecution2.cpp -o ZwDelayExecution2.exe -lntdll
This will automatically use the file “libntdll.a”, GCC will strip the preceding “lib” and succeeding “.a”. If you have another file with a different name, use can use -l:<name> to write <name> as the exact file name.
Do we have to include “-lkernel32” when using kernel32.dll APIs? Nope, GCC will link against kernel32 by default for you. GCC links against some default libraries, and if you want to know what they are in your version, you can add -v option when you compile. Mine shows those libraries been linked against by default:
-lmsvcrt -ladvapi32 -lshell32 -luser32 -lkernel32
Let’s say, MinGW doesn’t have libntdll.a file. Well, you can create the file using the following steps:
First generate a .def file using the tool pexports that comes with MinGW
pexports ntdll.dll | sed “s/^_//” > ntdll.def
Then, generate the library file using dlltool (you should find it in bin directory of your MinGW)
dlltool -U -d ntdll.def -l libntdll.a
Option 2 – Using CL:
cl ZwDelayExecution2.cpp kernel32.lib ntdll.lib user32.lib
CL needs the name of every library you’re using any of its APIs. So in our case we need kernel32.lib, user32.lib and ntdll.lib.
Unlike MinGW’s GCC, CL will not load default libraries for you unless you say so. Check out /DEFAULTLIB:library option.
But wait! .. you cannot find ntdll.lib file, right?! You’ve got “LINK : fatal error LNK1181: cannot open input file ‘ntdll.lib‘” .. Well, yes, Microsoft SDK that comes with Visual Studio doesn’t have ntdll.lib. That’s Microsoft’s way to force you to use Method 1 (The dynamic linking). But don’t panic, there are several ways to get the ntdll.lib file.
1st Way, get it from MinGW directory. As I said earlier, MinGW comes with ntdll.lib, in this case the file name is libntdll.a. Just add the name as is in the command like this.
cl ZwDelayExecution2.cpp kernel32.lib C:\MinGW\mingw\lib\libntdll.a user32.lib
CL will give you a little warning “cl : Command line warning D9024 : unrecognized source file type ‘C:\MinGW\mingw\lib\libntdll.a’, object file assumed“, but will compile it right.
2nd Way, if the 1st way didn’t work with your version of CL, try to follow these helpful steps here. http://www.mingw.org/wiki/msvc_and_mingw_dlls
3rd Way, use the Python method explained in this awesome reply.
4th Way, If you have the DDK/WDK – for Driver Development Kit and Windows Driver Kit respectively – you should find ntdll.lib files for different architectures there, pick yours.
That should cover linking against the library. Last thing, what about the line #18 in the source code of ZwDelayExecution2.cpp?. Well, as I said, Microsoft doesn’t want you to use the internal APIs in the static way, so the header file for ntdll.dll “winternl.h” is very minimal and doesn’t contain definitions for every function in the library. That’s why in some cases we need to declare the function signature ourselves.
That should cover all!.