3

I'm writing a low-level logger function that appends text string to the end of a text (log) file. The requirement is that this function should not invoke any WinAPIs from DLLs that may not be yet available for the process -- such as when it's called from a DllMain handler. In other words, it can't use any libraries other than the ones that are guaranteed to be loaded into any user-mode process, i.e. kernel32.dll or ntdll.dll.

I was able to get by quite nicely with just CreateFile, WriteFile, CloseHandle, HeapAlloc, HeapFree, etc. that are all from kernel32.dll.

The issue is formatting the output string. For instance, I need to add some additional (automatically generated) details, such as current time, process ID, session ID, etc. I would normally use wsprintf type function for that, or StringCchPrintf to be exact, as such:

StringCchPrintf(buffer, buffer_size, L"%04u-%02u-%02u %02u:%02u:%02u pid=0x%x, sessID=%d, %s\r\n", /* parameters */ );

but those APIs violate the rule I noted above.

Does anyone know if there's a low level printf type formatting API available?

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 2
    `sprintf` and `swprintf` exported from *ntdll*. you can free use it. only one restriction - it not support float format – RbMm Oct 20 '17 at 21:08
  • @RbMm: Hmm, interesting I thought those are CRT functions. Where do you take that from? Also what happens if I use float format? – c00000fd Oct 20 '17 at 21:16
  • 1
    ntdll support almost all string formatting functions, not only this 2, but `_snprintf`, `_snwprintf`, `_vsnprintf`, `_vsnwprintf`, .. if you will use `%f` or `%g` - it simply not process it – RbMm Oct 20 '17 at 21:19

1 Answers1

3

all versions of ntdll.dll support how minimum next(from xp) string formating functions:

_snprintf
_snwprintf
_vsnprintf
_vsnwprintf
sprintf
swprintf
vsprintf

the signatures of course full matches same functions from crt. we can free use this api. new versions of ntdll add some new format string api. say win7 (and all latest version) ntdll.dll export next:

_snprintf
_snprintf_s
_snwprintf
_snwprintf_s
_swprintf
_vscwprintf
_vsnprintf
_vsnprintf_s
_vsnwprintf
_vsnwprintf_s
_vswprintf
swprintf
swprintf_s
vsprintf
vsprintf_s
vswprintf_s
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • OK. Good to know. Thanks. One question though, how do you make it link to the function in `ntdll.dll` instead of its CRT counterpart? Most of them are named the same. – c00000fd Oct 20 '17 at 22:44
  • @c00000fd - at first lib order is important. if some function can be found in more than one lib - will be used lib which first in order. so you need use `ntdll.lib` before any crt libs. however very possible will be some conflict between crt libs and ntdll (this depend from which crt lib you use). i have no this problems because i never use crt. if will be serious conflict between ntdll and crt libs - you how minimum can get address of this procs in runtime – RbMm Oct 20 '17 at 22:55
  • 1
    Note: The print functions in NTDLL do not support floats/doubles. – Anders Oct 20 '17 at 23:34
  • 1
    Alternatively, since kernel32.dll is allowed here, you can use `FormatMessageW` with `FORMAT_MESSAGE_FROM_STRING` and its `%n!format string!` inserts. There's still no floating point support, however. – Eryk Sun Oct 20 '17 at 23:34
  • @Anders - yes, i say this at begin in comment under question – RbMm Oct 20 '17 at 23:36
  • @eryksun: Hmm, interesting. `FormatMessageW` is a good candidate. I tried to use `_vsnwprintf` and `swprintf` functions exported from `ntdll.dll` as RbMm suggested, but unfortunately I can't seem to make Visual Studio C++ linker to link them to `ntdll.dll` instead of the CRT. Unfortunately, to load them at runtime, I need to use `GetModuleHandle(L"ntdll.dll");` which [seem to be risky](https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971(v=vs.85).aspx#general_best_practices) to call from `DllMain`. – c00000fd Oct 21 '17 at 02:14
  • @RbMm: Well, don't get mad. I'm just basing it on [what Microsoft said](https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971(v=vs.85).aspx#deadlocks_caused_by_lock_order_inversion): Search for the phrase: `"...then calls a function such as GetModuleHandle that attempts to acquire the loader lock..."`. What are you basing your assumptions on, can you explain? – c00000fd Oct 21 '17 at 08:13
  • @c00000fd - sorry, but you nothing understand here absolute. the source of error that thread inside of loader lock begin **wait** for another object - `Lock G`. this lock is held for another thread. and this another thread never release this lock because it begin wait for loader lock by call `GetModuleHandle` - and note this call made by thread not inside loader lock. but again and again - the source of deadlock - is **WAIT** inside lock. if you call `GetModuleHandle` inside loader lock this not cause any wait - you already hold this lock. so this call is **ABSOLUTE** safe here – RbMm Oct 21 '17 at 08:21
  • @c00000fd - so at first, as general note `GetModuleHandle` is always safe and legal from loader lock. at second use ntdll formating functions much more better and easy that use something like `FormatMessageW`. at third unconditional possible static import this api from ntdll.dll. how i say you need disable all default libs (/nodefaullib) and manual add crt lib to library list. and at first place at this list must be ntdll.lib. need not just say this not worked if first attemp fail and search another ways, but try this way until win – RbMm Oct 21 '17 at 08:26
  • @RbMm: Well, OK, yes I see your point. In their example `GetModuleHandle` is called from another thread than `DllMain`. I guess it was just a confusing sentence on that page. Thanks for pointing it out. – c00000fd Oct 21 '17 at 08:27
  • As for disabling default libs `/nodefaullib` -- I'm not creating a project. It's just a helper logging class that can be used somewhere else. A general case, so to speak. – c00000fd Oct 21 '17 at 08:28
  • @c00000fd - `GetModuleHandle` here only what cause another thread (which hold lock G) wait for loader lock. thread which is hold loader lock is wait for G. deadlock can be when and only when we wait inside lock. so wait is very bad inside lock. and if we wait for another thread from our process or object which can be acquired/released by another thread from **our** process, which can acquire loader lock - this is fatal. deadlock - this is always 2 wait – RbMm Oct 21 '17 at 08:32
  • @c00000fd - about lib - which is you current linker lib list ? you need build with `/VERBOSE` option and look log. this problem of course can be resolved – RbMm Oct 21 '17 at 08:33
  • @RbMm: Yes, I see your point about a deadlock. As for your second question, I don't have a project for it. It's just a C++ class in a text file at this point. But if I could somehow force the Visual Studio linker **only** to link `_vsnwprintf` and `swprintf` functions to `ntdll.dll` and leave the rest of the CRT functions as-is, that would be great. But I don't think there's such an option, is there? – c00000fd Oct 21 '17 at 08:39
  • @c00000fd - *I don't have a project for it. It's just a C++ class in a text file at this point* - but how you link ? you need somehow invoke linker for build. what i say - that *ntdll.lib* must be first in linker input and `/NODEFAULTLIB` option. and not use `/DEFAULTLIB:library`. this is unconditionally possible - import this api from ntdll.lib. simply need bit more understand linker and not just say *not work* if from first attempt not ok, but insist – RbMm Oct 21 '17 at 08:44
  • @RbMm: OK. I'll try it when I build it into a test project. – c00000fd Oct 21 '17 at 08:47
  • If this is supposed to be a utility class for other projects, then you really should not force a custom linker configuration. Use dynamic linking via `GetModuleHandleW` and `GetProcAddress`. It's trivial to typedef a couple of functions. My suggestion to use `FormatMessageW` was more about avoiding ntdll.lib, which requires installing the WDK, and also to strictly stay within the Windows API rather than relying on undocumented exports of ntdll. Of course, internally it calls ntdll's `RtlFormatMessage[Ex]` and `_vsnwprintf`. – Eryk Sun Oct 21 '17 at 15:04
  • @eryksun - `FormatMessageW` use another format specification, different from standard `sprint` - `%1`, `%2` etc.. how here set the type of argument ? if for example I want `L"%s(%u):"` format - how this must look like in `FormatMessageW` ? – RbMm Oct 21 '17 at 15:21
  • It's different, but not all that different. It uses exclamation marks to mark a format specification, e.g. `%1!*.*s!`, and `%!` can be used to add an exclamation mark immediately after an insert, e.g. `%1%!`. – Eryk Sun Oct 21 '17 at 15:26
  • @eryksun - yes, can in principle use this too, but i personally prefer use *[_v]s[nw]printf* here. dont understand why ntdll so tabu – RbMm Oct 21 '17 at 15:47
  • I appreciate your input, guys! – c00000fd Oct 21 '17 at 20:01
  • None of these are supported. Without any documentation, you don't know what the contract is. You cannot ever know, whether you are using those functions within their specification. There is no specification. – IInspectable Dec 20 '19 at 13:56