Delayed Loading of RapidCode.dll

[RMP 10.3.1] [INtime 6.4.21125.2]

The MS linker supports a flag, /DELAYLOAD:<binary>

I would like to leverage this behavior WRT RapidCode.dll so that my application (a plugin for another application that I do not maintain) won’t attempt to load the DLL (and its dependents) unless I actually need to. If my plugin is not enabled, I don’t want to load things. If it gets loaded on a system where it won’t work, I would prefer not to load things. I also want to be able to add directories to the search list for DLLs without requiring that all the dependent DLLs not be in PATH or copied to the CWD.

Knowing when to load is all defined by me. Updating the search path is on me.

My question for you is, do you have any advice regarding this scenario?

What I’ve Done

  • I added /DELAYLOAD:RapidCode.dll to the linker command line for my project.
  • I added %RSI%\x86 and \path\to\intime\bin to the DLL search path.

Loading things this way works fine. I can run and do all the same stuff I could do before I added delayed loading.

However, when the application shuts down, I’m getting crashes inside ntx.dll. Here’s what WinDBG reports as the stack trace.

> kv
008ff998 7ff026c0 00000000 8939ee7d 7ff02534 ntx!_ntxRegSetValueExW+0x3936
008ff9d0 7ff0259a 00000000 008ff9fc 77ec2976 ntx!SetLastNtxError+0x1194
008ff9dc 77ec2976 7ff00000 00000000 00000001 ntx!SetLastNtxError+0x106e
008ff9fc 77e9dd22 7ff02534 7ff00000 00000000 ntdll!LdrxCallInitRoutine+0x16
008ffa48 77ead7c5 00000000 00000001 c0d1899d ntdll!LdrpCallInitRoutine+0x51 (FPO: [Non-Fpo])
008ffae0 77ead6b5 00000000 00000000 007b9000 ntdll!LdrShutdownProcess+0xf5 (FPO: [SEH])
008ffbb0 76204112 00000000 77e8f3b0 ffffffff ntdll!RtlExitUserProcess+0xb5 (FPO: [Non-Fpo])
008ffbc4 7728451c 00000000 00000000 772843f0 KERNEL32!ExitProcessImplementation+0x12 (FPO: [1,0,0])
008ffbd0 772843f0 00000000 008ffbf0 008ffc00 ucrtbase!exit_or_terminate_process+0x20 (FPO: [0,0,4])
008ffbf8 77284391 00000000 008ffc44 038105a1 ucrtbase!common_exit+0x5c (FPO: [Non-Fpo])
...

By this time in the life of the application, all my stuff has been cleaned up. (For the sake of argument, let’s say I did it all correctly.) There are no other threads running. There shouldn’t be anything using resources within that module.

What’s up with the crash?

Do you have any idea what INtime’s DLL is doing?

The (possible) parameters to the called functions don’t obviously mean anything to me.

Is there some sort of cleanup that might get INtime not to do whatever it’s doing?

Ideas?

  • I’ve tried manually unloading both RapidCode.dll and ntx.dll in the very last bit of code that runs in my plugin.
  • I added a breakpoint at this point and manually/forcibly unloaded ntx.dll in the debugger.
    • This crashed, unsurprisingly, when some kernel function tried to execute code within the module’s former address space.
  • I considered switching ntx.dll to being delay loaded, but in order for that to work, I actually have to link against it. I don’t have an import library (though I can easily create one), and what’s worse, I don’t have a header file.

Additional Note

I was having trouble with delayed loading until I forcibly loading RapidCode.dll right after updating the DLL search directories.

AddDllDirectory(util::to_wstring(RSI_DLL.string()).c_str());
AddDllDirectory(INTIME_DLL.c_str());
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
LoadLibrary(L"RapidCode.dll");

After that, it worked OK.

Are you running with admin rights? Not that elevation should be required, but I’m noticing that ntx registry call. I’m curious if that makes a difference.

We’re not running in an elevated context.

IDK why the DLL would croak inside a function that, on the surface, looks like a wrapper around a Win32 function, RegSetValueEx(…).

A very crude attempt to look for meaningful data on the stack did not identify any strings, so IDK what value name might be the target. The values between 0x…998 and 0x…9d0 don’t obviously look like strings or other things I’m familiar with. It didn’t get as far as calling the Win32 API function.
The first parameter to the last ntx function is 0, which probably doesn’t correspond to a Win32 HKEY, but if it did that would be bad.

The actual exception is an access violation.

> .exr -1
ExceptionAddress: 7ff14f8c (ntx!_ntxRegSetValueExW+0x00003936)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 00000004
Attempt to read from address 00000004

I don’t see 4 in the stack at all as a DWORD, so who knows.

Anyway, are you doing anything with NTX or the INtime runtime on module load/unload or init/cleanup?

I set a breakpoint in that function, and then realized that it might never be called (which it wasn’t). The offset “into” the function xs 0x3936, which just means that that function is the closest thing to where the thread actually was.

We have seen some similar Access Violations with some of our tests when we are exiting. I am working with Tenasys to try to pin this down.

To answer your question though, we don’t do anything special with ntx at runtime. We just link against the library and load the binary (in a non-delayed fashion) during our application startup.

1 Like

Thanks, Stefan. Only you can work with TenAsys. (Or, more precisely, TenAsys will only work with you.)

My tests of the updated/hotfixed ntx.dll make me think that they’ve fixed the problem. Any idea when this behavior might exist in a published release?