HyperPlatform Programmer's Reference
log.cpp
Go to the documentation of this file.
1 // Copyright (c) 2015-2018, Satoshi Tanda. All rights reserved.
2 // Use of this source code is governed by a MIT-style license that can be
3 // found in the LICENSE file.
4 
7 
8 #include "log.h"
9 #define NTSTRSAFE_NO_CB_FUNCTIONS
10 #include <ntstrsafe.h>
11 
12 // See common.h for details
13 #pragma prefast(disable : 30030)
14 
15 extern "C" {
17 //
18 // macro utilities
19 //
20 
22 //
23 // constant and macro
24 //
25 
26 // A size for log buffer in NonPagedPool. Two buffers are allocated with this
27 // size. Exceeded logs are ignored silently. Make it bigger if a buffered log
28 // size often reach this size.
29 static const auto kLogpBufferSizeInPages = 16ul;
30 
31 // An actual log buffer size in bytes.
32 static const auto kLogpBufferSize = PAGE_SIZE * kLogpBufferSizeInPages;
33 
34 // A size that is usable for logging. Minus one because the last byte is kept
35 // for \0.
36 static const auto kLogpBufferUsableSize = kLogpBufferSize - 1;
37 
38 // An interval to flush buffered log entries into a log file.
39 static const auto kLogpLogFlushIntervalMsec = 50;
40 
41 static const ULONG kLogpPoolTag = ' gol';
42 
44 //
45 // types
46 //
47 
48 struct LogBufferInfo {
49  // A pointer to buffer currently used. It is either log_buffer1 or
50  // log_buffer2.
51  volatile char *log_buffer_head;
52 
53  // A pointer to where the next log should be written.
54  volatile char *log_buffer_tail;
55 
56  char *log_buffer1;
57  char *log_buffer2;
58 
59  // Holds the biggest buffer usage to determine a necessary buffer size.
60  SIZE_T log_max_usage;
61 
63  KSPIN_LOCK spin_lock;
64  ERESOURCE resource;
69  wchar_t log_file_path[200];
70 };
71 
73 //
74 // prototypes
75 //
76 
77 NTKERNELAPI UCHAR *NTAPI PsGetProcessImageFileName(_In_ PEPROCESS process);
78 
79 _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS
80  LogpInitializeBufferInfo(_In_ const wchar_t *log_file_path,
81  _Inout_ LogBufferInfo *info);
82 
83 _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS
84  LogpInitializeLogFile(_Inout_ LogBufferInfo *info);
85 
86 static DRIVER_REINITIALIZE LogpReinitializationRoutine;
87 
88 _IRQL_requires_max_(PASSIVE_LEVEL) static void LogpFinalizeBufferInfo(
89  _In_ LogBufferInfo *info);
90 
91 static NTSTATUS LogpMakePrefix(_In_ ULONG level,
92  _In_z_ const char *function_name,
93  _In_z_ const char *log_message,
94  _Out_ char *log_buffer,
95  _In_ SIZE_T log_buffer_length);
96 
97 static const char *LogpFindBaseFunctionName(_In_z_ const char *function_name);
98 
99 static NTSTATUS LogpPut(_In_z_ char *message, _In_ ULONG attribute);
100 
101 _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS
102  LogpFlushLogBuffer(_Inout_ LogBufferInfo *info);
103 
104 _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS
105  LogpWriteMessageToFile(_In_z_ const char *message,
106  _In_ const LogBufferInfo &info);
107 
108 static NTSTATUS LogpBufferMessage(_In_z_ const char *message,
109  _Inout_ LogBufferInfo *info);
110 
111 static void LogpDoDbgPrint(_In_z_ char *message);
112 
113 static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &info);
114 
115 static bool LogpIsLogFileActivated(_In_ const LogBufferInfo &info);
116 
117 static bool LogpIsLogNeeded(_In_ ULONG level);
118 
119 static bool LogpIsDbgPrintNeeded();
120 
121 static KSTART_ROUTINE LogpBufferFlushThreadRoutine;
122 
123 _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS
124  LogpSleep(_In_ LONG millisecond);
125 
126 static void LogpSetPrintedBit(_In_z_ char *message, _In_ bool on);
127 
128 static bool LogpIsPrinted(_In_z_ char *message);
129 
130 static void LogpDbgBreak();
131 
132 #if defined(ALLOC_PRAGMA)
133 #pragma alloc_text(INIT, LogInitialization)
134 #pragma alloc_text(INIT, LogpInitializeBufferInfo)
135 #pragma alloc_text(PAGE, LogpInitializeLogFile)
136 #pragma alloc_text(INIT, LogRegisterReinitialization)
137 #pragma alloc_text(PAGE, LogpReinitializationRoutine)
138 #pragma alloc_text(PAGE, LogIrpShutdownHandler)
139 #pragma alloc_text(PAGE, LogTermination)
140 #pragma alloc_text(PAGE, LogpFinalizeBufferInfo)
141 #pragma alloc_text(PAGE, LogpBufferFlushThreadRoutine)
142 #pragma alloc_text(PAGE, LogpSleep)
143 #endif
144 
146 //
147 // variables
148 //
149 
152 
154 //
155 // implementations
156 //
157 
158 _Use_decl_annotations_ NTSTATUS
159 LogInitialization(ULONG flag, const wchar_t *log_file_path) {
160  PAGED_CODE();
161 
162  auto status = STATUS_SUCCESS;
163 
164  g_logp_debug_flag = flag;
165 
166  // Initialize a log file if a log file path is specified.
167  bool need_reinitialization = false;
168  if (log_file_path) {
169  status = LogpInitializeBufferInfo(log_file_path, &g_logp_log_buffer_info);
170  if (status == STATUS_REINITIALIZATION_NEEDED) {
171  need_reinitialization = true;
172  } else if (!NT_SUCCESS(status)) {
173  return status;
174  }
175  }
176 
177  // Test the log.
178  status = HYPERPLATFORM_LOG_INFO("Log has been %sinitialized.",
179  (need_reinitialization ? "partially " : ""));
180  if (!NT_SUCCESS(status)) {
181  goto Fail;
182  }
183  HYPERPLATFORM_LOG_DEBUG("Info= %p, Buffer= %p %p, File= %S",
186  g_logp_log_buffer_info.log_buffer2, log_file_path);
187  return (need_reinitialization ? STATUS_REINITIALIZATION_NEEDED
188  : STATUS_SUCCESS);
189 
190 Fail:;
191  if (log_file_path) {
193  }
194  return status;
195 }
196 
197 // Initialize a log file related code such as a flushing thread.
198 _Use_decl_annotations_ static NTSTATUS LogpInitializeBufferInfo(
199  const wchar_t *log_file_path, LogBufferInfo *info) {
200  PAGED_CODE();
201  NT_ASSERT(log_file_path);
202  NT_ASSERT(info);
203 
204  KeInitializeSpinLock(&info->spin_lock);
205 
206  auto status = RtlStringCchCopyW(
207  info->log_file_path, RTL_NUMBER_OF_FIELD(LogBufferInfo, log_file_path),
208  log_file_path);
209  if (!NT_SUCCESS(status)) {
210  return status;
211  }
212 
213  status = ExInitializeResourceLite(&info->resource);
214  if (!NT_SUCCESS(status)) {
215  return status;
216  }
217  info->resource_initialized = true;
218 
219  // Allocate two log buffers on NonPagedPool.
220  info->log_buffer1 = reinterpret_cast<char *>(
221  ExAllocatePoolWithTag(NonPagedPool, kLogpBufferSize, kLogpPoolTag));
222  if (!info->log_buffer1) {
224  return STATUS_INSUFFICIENT_RESOURCES;
225  }
226 
227  info->log_buffer2 = reinterpret_cast<char *>(
228  ExAllocatePoolWithTag(NonPagedPool, kLogpBufferSize, kLogpPoolTag));
229  if (!info->log_buffer2) {
231  return STATUS_INSUFFICIENT_RESOURCES;
232  }
233 
234  // Initialize these buffers
235  RtlFillMemory(info->log_buffer1, kLogpBufferSize, 0xff); // for diagnostic
236  info->log_buffer1[0] = '\0';
237  info->log_buffer1[kLogpBufferSize - 1] = '\0'; // at the end
238 
239  RtlFillMemory(info->log_buffer2, kLogpBufferSize, 0xff); // for diagnostic
240  info->log_buffer2[0] = '\0';
241  info->log_buffer2[kLogpBufferSize - 1] = '\0'; // at the end
242 
243  // Buffer should be used is log_buffer1, and location should be written logs
244  // is the head of the buffer.
245  info->log_buffer_head = info->log_buffer1;
246  info->log_buffer_tail = info->log_buffer1;
247 
248  status = LogpInitializeLogFile(info);
249  if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
250  HYPERPLATFORM_LOG_INFO("The log file needs to be activated later.");
251  status = STATUS_REINITIALIZATION_NEEDED;
252  } else if (!NT_SUCCESS(status)) {
254  }
255  return status;
256 }
257 
258 // Initializes a log file and starts a log buffer thread.
259 _Use_decl_annotations_ static NTSTATUS LogpInitializeLogFile(
260  LogBufferInfo *info) {
261  PAGED_CODE();
262 
263  if (info->log_file_handle) {
264  return STATUS_SUCCESS;
265  }
266 
267  // Initialize a log file
268  UNICODE_STRING log_file_path_u = {};
269  RtlInitUnicodeString(&log_file_path_u, info->log_file_path);
270 
271  OBJECT_ATTRIBUTES oa = {};
272  InitializeObjectAttributes(&oa, &log_file_path_u,
273  OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr,
274  nullptr);
275 
276  IO_STATUS_BLOCK io_status = {};
277  auto status = ZwCreateFile(
278  &info->log_file_handle, FILE_APPEND_DATA | SYNCHRONIZE, &oa, &io_status,
279  nullptr, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF,
280  FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, nullptr, 0);
281  if (!NT_SUCCESS(status)) {
282  return status;
283  }
284 
285  // Initialize a log buffer flush thread.
287  status = PsCreateSystemThread(&info->buffer_flush_thread_handle, GENERIC_ALL,
288  nullptr, nullptr, nullptr,
290  if (!NT_SUCCESS(status)) {
291  ZwClose(info->log_file_handle);
292  info->log_file_handle = nullptr;
294  return status;
295  }
296 
297  // Wait until the thread has started
298  while (!info->buffer_flush_thread_started) {
299  LogpSleep(100);
300  }
301  return status;
302 }
303 
304 // Registers LogpReinitializationRoutine() for re-initialization.
305 _Use_decl_annotations_ void LogRegisterReinitialization(
306  PDRIVER_OBJECT driver_object) {
307  PAGED_CODE();
308  IoRegisterBootDriverReinitialization(
310  HYPERPLATFORM_LOG_INFO("The log file will be activated later.");
311 }
312 
313 // Initializes a log file at the re-initialization phase.
314 _Use_decl_annotations_ VOID static LogpReinitializationRoutine(
315  _DRIVER_OBJECT *driver_object, PVOID context, ULONG count) {
316  PAGED_CODE();
317  UNREFERENCED_PARAMETER(driver_object);
318  UNREFERENCED_PARAMETER(count);
319  NT_ASSERT(context);
320  _Analysis_assume_(context);
321 
322  auto info = reinterpret_cast<LogBufferInfo *>(context);
323  auto status = LogpInitializeLogFile(info);
324  NT_ASSERT(NT_SUCCESS(status));
325  if (NT_SUCCESS(status)) {
326  HYPERPLATFORM_LOG_INFO("The log file has been activated.");
327  }
328 }
329 
330 // Terminates the log functions without releasing resources.
331 _Use_decl_annotations_ void LogIrpShutdownHandler() {
332  PAGED_CODE();
333 
334  HYPERPLATFORM_LOG_DEBUG("Flushing... (Max log usage = %Iu/%lu bytes)",
337  HYPERPLATFORM_LOG_INFO("Bye!");
339 
340  // Wait until the log buffer is emptied.
341  auto &info = g_logp_log_buffer_info;
342  while (info.log_buffer_head[0]) {
344  }
345 }
346 
347 // Terminates the log functions.
348 _Use_decl_annotations_ void LogTermination() {
349  PAGED_CODE();
350 
351  HYPERPLATFORM_LOG_DEBUG("Finalizing... (Max log usage = %Iu/%lu bytes)",
354  HYPERPLATFORM_LOG_INFO("Bye!");
357 }
358 
359 // Terminates a log file related code.
360 _Use_decl_annotations_ static void LogpFinalizeBufferInfo(LogBufferInfo *info) {
361  PAGED_CODE();
362  NT_ASSERT(info);
363 
364  // Closing the log buffer flush thread.
365  if (info->buffer_flush_thread_handle) {
367  auto status =
368  ZwWaitForSingleObject(info->buffer_flush_thread_handle, FALSE, nullptr);
369  if (!NT_SUCCESS(status)) {
370  LogpDbgBreak();
371  }
372  ZwClose(info->buffer_flush_thread_handle);
373  info->buffer_flush_thread_handle = nullptr;
374  }
375 
376  // Cleaning up other things.
377  if (info->log_file_handle) {
378  ZwClose(info->log_file_handle);
379  info->log_file_handle = nullptr;
380  }
381  if (info->log_buffer2) {
382  ExFreePoolWithTag(info->log_buffer2, kLogpPoolTag);
383  info->log_buffer2 = nullptr;
384  }
385  if (info->log_buffer1) {
386  ExFreePoolWithTag(info->log_buffer1, kLogpPoolTag);
387  info->log_buffer1 = nullptr;
388  }
389 
390  if (info->resource_initialized) {
391  ExDeleteResourceLite(&info->resource);
392  info->resource_initialized = false;
393  }
394 }
395 
396 // Actual implementation of logging API.
397 _Use_decl_annotations_ NTSTATUS LogpPrint(ULONG level,
398  const char *function_name,
399  const char *format, ...) {
400  auto status = STATUS_SUCCESS;
401 
402  if (!LogpIsLogNeeded(level)) {
403  return status;
404  }
405 
406  va_list args;
407  va_start(args, format);
408  char log_message[412];
409  status = RtlStringCchVPrintfA(log_message, RTL_NUMBER_OF(log_message), format,
410  args);
411  va_end(args);
412  if (!NT_SUCCESS(status)) {
413  LogpDbgBreak();
414  return status;
415  }
416  if (log_message[0] == '\0') {
417  LogpDbgBreak();
418  return STATUS_INVALID_PARAMETER;
419  }
420 
421  const auto pure_level = level & 0xf0;
422  const auto attribute = level & 0x0f;
423 
424  // A single entry of log should not exceed 512 bytes. See
425  // Reading and Filtering Debugging Messages in MSDN for details.
426  char message[512];
427  static_assert(RTL_NUMBER_OF(message) <= 512,
428  "One log message should not exceed 512 bytes.");
429  status = LogpMakePrefix(pure_level, function_name, log_message, message,
430  RTL_NUMBER_OF(message));
431  if (!NT_SUCCESS(status)) {
432  LogpDbgBreak();
433  return status;
434  }
435 
436  status = LogpPut(message, attribute);
437  if (!NT_SUCCESS(status)) {
438  LogpDbgBreak();
439  }
440  return status;
441 }
442 
443 // Concatenates meta information such as the current time and a process ID to
444 // user given log message.
445 _Use_decl_annotations_ static NTSTATUS LogpMakePrefix(
446  ULONG level, const char *function_name, const char *log_message,
447  char *log_buffer, SIZE_T log_buffer_length) {
448  char const *level_string = nullptr;
449  switch (level) {
450  case kLogpLevelDebug:
451  level_string = "DBG\t";
452  break;
453  case kLogpLevelInfo:
454  level_string = "INF\t";
455  break;
456  case kLogpLevelWarn:
457  level_string = "WRN\t";
458  break;
459  case kLogpLevelError:
460  level_string = "ERR\t";
461  break;
462  default:
463  return STATUS_INVALID_PARAMETER;
464  }
465 
466  auto status = STATUS_SUCCESS;
467 
468  char time_buffer[20] = {};
469  if ((g_logp_debug_flag & kLogOptDisableTime) == 0) {
470  // Want the current time.
471  TIME_FIELDS time_fields;
472  LARGE_INTEGER system_time, local_time;
473  KeQuerySystemTime(&system_time);
474  ExSystemTimeToLocalTime(&system_time, &local_time);
475  RtlTimeToTimeFields(&local_time, &time_fields);
476 
477  status = RtlStringCchPrintfA(time_buffer, RTL_NUMBER_OF(time_buffer),
478  "%02hd:%02hd:%02hd.%03hd\t", time_fields.Hour,
479  time_fields.Minute, time_fields.Second,
480  time_fields.Milliseconds);
481  if (!NT_SUCCESS(status)) {
482  return status;
483  }
484  }
485 
486  // Want the function name
487  char function_name_buffer[50] = {};
489  const auto base_function_name = LogpFindBaseFunctionName(function_name);
490  status = RtlStringCchPrintfA(function_name_buffer,
491  RTL_NUMBER_OF(function_name_buffer), "%-40s\t",
492  base_function_name);
493  if (!NT_SUCCESS(status)) {
494  return status;
495  }
496  }
497 
498  // Want the processor number
499  char processro_number[10] = {};
501  status =
502  RtlStringCchPrintfA(processro_number, RTL_NUMBER_OF(processro_number),
503  "#%lu\t", KeGetCurrentProcessorNumberEx(nullptr));
504  if (!NT_SUCCESS(status)) {
505  return status;
506  }
507  }
508 
509  // It uses PsGetProcessId(PsGetCurrentProcess()) instead of
510  // PsGetCurrentThreadProcessId() because the later sometimes returns
511  // unwanted value, for example:
512  // PID == 4 but its image name != ntoskrnl.exe
513  // The author is guessing that it is related to attaching processes but
514  // not quite sure. The former way works as expected.
515  status = RtlStringCchPrintfA(
516  log_buffer, log_buffer_length, "%s%s%s%5Iu\t%5Iu\t%-15s\t%s%s\r\n",
517  time_buffer, level_string, processro_number,
518  reinterpret_cast<ULONG_PTR>(PsGetProcessId(PsGetCurrentProcess())),
519  reinterpret_cast<ULONG_PTR>(PsGetCurrentThreadId()),
520  PsGetProcessImageFileName(PsGetCurrentProcess()), function_name_buffer,
521  log_message);
522  return status;
523 }
524 
525 // Returns the function's base name, for example,
526 // NamespaceName::ClassName::MethodName will be returned as MethodName.
527 _Use_decl_annotations_ static const char *LogpFindBaseFunctionName(
528  const char *function_name) {
529  if (!function_name) {
530  return nullptr;
531  }
532 
533  auto ptr = function_name;
534  auto name = function_name;
535  while (*(ptr++)) {
536  if (*ptr == ':') {
537  name = ptr + 1;
538  }
539  }
540  return name;
541 }
542 
543 // Logs the entry according to attribute and the thread condition.
544 _Use_decl_annotations_ static NTSTATUS LogpPut(char *message, ULONG attribute) {
545  auto status = STATUS_SUCCESS;
546 
547  auto do_DbgPrint = ((attribute & kLogpLevelOptSafe) == 0 &&
548  KeGetCurrentIrql() < CLOCK_LEVEL);
549 
550  // Log the entry to a file or buffer.
551  auto &info = g_logp_log_buffer_info;
552  if (LogpIsLogFileEnabled(info)) {
553  // Can it log it to a file now?
554  if (((attribute & kLogpLevelOptSafe) == 0) &&
555  KeGetCurrentIrql() == PASSIVE_LEVEL && LogpIsLogFileActivated(info)) {
556 #pragma warning(push)
557 #pragma warning(disable : 28123)
558  if (!KeAreAllApcsDisabled()) {
559  // Yes, it can. Do it.
560  LogpFlushLogBuffer(&info);
561  status = LogpWriteMessageToFile(message, info);
562  }
563 #pragma warning(pop)
564  } else {
565  // No, it cannot. Set the printed bit if needed, and then buffer it.
566  if (do_DbgPrint) {
567  LogpSetPrintedBit(message, true);
568  }
569  status = LogpBufferMessage(message, &info);
570  LogpSetPrintedBit(message, false);
571  }
572  }
573 
574  // Can it safely be printed?
575  if (do_DbgPrint) {
576  LogpDoDbgPrint(message);
577  }
578  return status;
579 }
580 
581 // Switches the current log buffer, saves the contents of old buffer to the log
582 // file, and prints them out as necessary. This function does not flush the log
583 // file, so code should call LogpWriteMessageToFile() or ZwFlushBuffersFile()
584 // later.
585 _Use_decl_annotations_ static NTSTATUS LogpFlushLogBuffer(LogBufferInfo *info) {
586  NT_ASSERT(info);
587  NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
588 
589  auto status = STATUS_SUCCESS;
590 
591  // Enter a critical section and acquire a reader lock for info in order to
592  // write a log file safely.
593  ExEnterCriticalRegionAndAcquireResourceExclusive(&info->resource);
594 
595  // Acquire a spin lock for info.log_buffer(s) in order to switch its head
596  // safely.
597  KLOCK_QUEUE_HANDLE lock_handle = {};
598  KeAcquireInStackQueuedSpinLock(&info->spin_lock, &lock_handle);
599  const auto old_log_buffer = const_cast<char *>(info->log_buffer_head);
600  info->log_buffer_head = (old_log_buffer == info->log_buffer1)
601  ? info->log_buffer2
602  : info->log_buffer1;
603  info->log_buffer_head[0] = '\0';
604  info->log_buffer_tail = info->log_buffer_head;
605  KeReleaseInStackQueuedSpinLock(&lock_handle);
606 
607  // Write all log entries in old log buffer.
608  IO_STATUS_BLOCK io_status = {};
609  for (auto current_log_entry = old_log_buffer; current_log_entry[0]; ) {
610  // Check the printed bit and clear it
611  const auto printed_out = LogpIsPrinted(current_log_entry);
612  LogpSetPrintedBit(current_log_entry, false);
613 
614  const auto current_log_entry_length = strlen(current_log_entry);
615  status = ZwWriteFile(info->log_file_handle, nullptr, nullptr, nullptr,
616  &io_status, current_log_entry,
617  static_cast<ULONG>(current_log_entry_length), nullptr,
618  nullptr);
619  if (!NT_SUCCESS(status)) {
620  // It could happen when you did not register IRP_SHUTDOWN and call
621  // LogIrpShutdownHandler() and the system tried to log to a file after
622  // a file system was unmounted.
623  LogpDbgBreak();
624  }
625 
626  // Print it out if requested and the message is not already printed out
627  if (!printed_out) {
628  LogpDoDbgPrint(current_log_entry);
629  }
630 
631  current_log_entry += current_log_entry_length + 1;
632  }
633  old_log_buffer[0] = '\0';
634 
635  ExReleaseResourceAndLeaveCriticalRegion(&info->resource);
636  return status;
637 }
638 
639 // Logs the current log entry to and flush the log file.
640 _Use_decl_annotations_ static NTSTATUS LogpWriteMessageToFile(
641  const char *message, const LogBufferInfo &info) {
642  NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
643 
644  IO_STATUS_BLOCK io_status = {};
645  auto status =
646  ZwWriteFile(info.log_file_handle, nullptr, nullptr, nullptr, &io_status,
647  const_cast<char *>(message),
648  static_cast<ULONG>(strlen(message)), nullptr, nullptr);
649  if (!NT_SUCCESS(status)) {
650  // It could happen when you did not register IRP_SHUTDOWN and call
651  // LogIrpShutdownHandler() and the system tried to log to a file after
652  // a file system was unmounted.
653  LogpDbgBreak();
654  }
655  status = ZwFlushBuffersFile(info.log_file_handle, &io_status);
656  return status;
657 }
658 
659 // Buffer the log entry to the log buffer.
660 _Use_decl_annotations_ static NTSTATUS LogpBufferMessage(const char *message,
661  LogBufferInfo *info) {
662  NT_ASSERT(info);
663 
664  // Acquire a spin lock to add the log safely.
665  KLOCK_QUEUE_HANDLE lock_handle = {};
666  const auto old_irql = KeGetCurrentIrql();
667  if (old_irql < DISPATCH_LEVEL) {
668  KeAcquireInStackQueuedSpinLock(&info->spin_lock, &lock_handle);
669  } else {
670  KeAcquireInStackQueuedSpinLockAtDpcLevel(&info->spin_lock, &lock_handle);
671  }
672  NT_ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
673 
674  // Copy the current log to the buffer.
675  SIZE_T used_buffer_size = info->log_buffer_tail - info->log_buffer_head;
676  auto status =
677  RtlStringCchCopyA(const_cast<char *>(info->log_buffer_tail),
678  kLogpBufferUsableSize - used_buffer_size, message);
679 
680  // Update info.log_max_usage if necessary.
681  if (NT_SUCCESS(status)) {
682  const auto message_length = strlen(message) + 1;
683  info->log_buffer_tail += message_length;
684  used_buffer_size += message_length;
685  if (used_buffer_size > info->log_max_usage) {
686  info->log_max_usage = used_buffer_size; // Update
687  }
688  } else {
689  info->log_max_usage = kLogpBufferSize; // Indicates overflow
690  }
691  *info->log_buffer_tail = '\0';
692 
693  if (old_irql < DISPATCH_LEVEL) {
694  KeReleaseInStackQueuedSpinLock(&lock_handle);
695  } else {
696  KeReleaseInStackQueuedSpinLockFromDpcLevel(&lock_handle);
697  }
698  return status;
699 }
700 
701 // Calls DbgPrintEx() while converting \r\n to \n\0
702 _Use_decl_annotations_ static void LogpDoDbgPrint(char *message) {
703  if (!LogpIsDbgPrintNeeded()) {
704  return;
705  }
706  const auto location_of_cr = strlen(message) - 2;
707  message[location_of_cr] = '\n';
708  message[location_of_cr + 1] = '\0';
709  DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s", message);
710 }
711 
712 // Returns true when a log file is enabled.
713 _Use_decl_annotations_ static bool LogpIsLogFileEnabled(
714  const LogBufferInfo &info) {
715  if (info.log_buffer1) {
716  NT_ASSERT(info.log_buffer2);
717  NT_ASSERT(info.log_buffer_head);
718  NT_ASSERT(info.log_buffer_tail);
719  return true;
720  }
721  NT_ASSERT(!info.log_buffer2);
722  NT_ASSERT(!info.log_buffer_head);
723  NT_ASSERT(!info.log_buffer_tail);
724  return false;
725 }
726 
727 // Returns true when a log file is opened.
728 _Use_decl_annotations_ static bool LogpIsLogFileActivated(
729  const LogBufferInfo &info) {
731  NT_ASSERT(info.buffer_flush_thread_handle);
732  NT_ASSERT(info.log_file_handle);
733  return true;
734  }
735  NT_ASSERT(!info.buffer_flush_thread_handle);
736  NT_ASSERT(!info.log_file_handle);
737  return false;
738 }
739 
740 // Returns true when logging is necessary according to the log's severity and
741 // a set log level.
742 _Use_decl_annotations_ static bool LogpIsLogNeeded(ULONG level) {
743  return !!(g_logp_debug_flag & level);
744 }
745 
746 // Returns true when DbgPrint is requested
747 /*_Use_decl_annotations_*/ static bool LogpIsDbgPrintNeeded() {
749 }
750 
751 // A thread runs as long as info.buffer_flush_thread_should_be_alive is true and
752 // flushes a log buffer to a log file every kLogpLogFlushIntervalMsec msec.
753 _Use_decl_annotations_ static VOID LogpBufferFlushThreadRoutine(
754  void *start_context) {
755  PAGED_CODE();
756  auto status = STATUS_SUCCESS;
757  auto info = reinterpret_cast<LogBufferInfo *>(start_context);
758  info->buffer_flush_thread_started = true;
759  HYPERPLATFORM_LOG_DEBUG("Log thread started (TID= %p).",
760  PsGetCurrentThreadId());
761 
762  while (info->buffer_flush_thread_should_be_alive) {
763  NT_ASSERT(LogpIsLogFileActivated(*info));
764  if (info->log_buffer_head[0]) {
765  NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
766  NT_ASSERT(!KeAreAllApcsDisabled());
767  status = LogpFlushLogBuffer(info);
768  // Do not flush the file for overall performance. Even a case of
769  // bug check, we should be able to recover logs by looking at both
770  // log buffers.
771  }
773  }
774  PsTerminateSystemThread(status);
775 }
776 
777 // Sleep the current thread's execution for milliseconds.
778 _Use_decl_annotations_ static NTSTATUS LogpSleep(LONG millisecond) {
779  PAGED_CODE();
780 
781  LARGE_INTEGER interval = {};
782  interval.QuadPart = -(10000ll * millisecond); // msec
783  return KeDelayExecutionThread(KernelMode, FALSE, &interval);
784 }
785 
786 // Marks the message as it is already printed out, or clears the printed bit and
787 // restores it to the original
788 _Use_decl_annotations_ static void LogpSetPrintedBit(char *message, bool on) {
789  if (on) {
790  message[0] |= 0x80;
791  } else {
792  message[0] &= 0x7f;
793  }
794 }
795 
796 // Tests if the printed bit is on
797 _Use_decl_annotations_ static bool LogpIsPrinted(char *message) {
798  return (message[0] & 0x80) != 0;
799 }
800 
801 // Sets a break point that works only when a debugger is present
802 /*_Use_decl_annotations_*/ static void LogpDbgBreak() {
803  if (!KD_DEBUGGER_NOT_PRESENT) {
804  __debugbreak();
805  }
806 }
807 
808 // Provides an implementation of _vsnprintf as it fails to link when a include
809 // directory setting is modified for using STL
810 _Success_(return >= 0) _Check_return_opt_ int __cdecl __stdio_common_vsprintf(
811  _In_ unsigned __int64 _Options,
812  _Out_writes_opt_z_(_BufferCount) char *_Buffer, _In_ size_t _BufferCount,
813  _In_z_ _Printf_format_string_params_(2) char const *_Format,
814  _In_opt_ _locale_t _Locale, va_list _ArgList) {
815  UNREFERENCED_PARAMETER(_Options);
816  UNREFERENCED_PARAMETER(_Locale);
817 
818  // Calls _vsnprintf exported by ntoskrnl
819  using _vsnprintf_type = int __cdecl(char *, size_t, const char *, va_list);
820  static _vsnprintf_type *local__vsnprintf = nullptr;
821  if (!local__vsnprintf) {
822  UNICODE_STRING proc_name_U = {};
823  RtlInitUnicodeString(&proc_name_U, L"_vsnprintf");
824  local__vsnprintf = reinterpret_cast<_vsnprintf_type *>(
825  MmGetSystemRoutineAddress(&proc_name_U));
826  }
827  return local__vsnprintf(_Buffer, _BufferCount, _Format, _ArgList);
828 }
829 
830 // Provides an implementation of _vsnwprintf as it fails to link when a include
831 // directory setting is modified for using STL
832 _Success_(return >= 0) _Check_return_opt_ int __cdecl __stdio_common_vswprintf(
833  _In_ unsigned __int64 _Options,
834  _Out_writes_opt_z_(_BufferCount) wchar_t *_Buffer, _In_ size_t _BufferCount,
835  _In_z_ _Printf_format_string_params_(2) wchar_t const *_Format,
836  _In_opt_ _locale_t _Locale, va_list _ArgList) {
837  UNREFERENCED_PARAMETER(_Options);
838  UNREFERENCED_PARAMETER(_Locale);
839 
840  // Calls _vsnwprintf exported by ntoskrnl
841  using _vsnwprintf_type =
842  int __cdecl(wchar_t *, size_t, const wchar_t *, va_list);
843  static _vsnwprintf_type *local__vsnwprintf = nullptr;
844  if (!local__vsnwprintf) {
845  UNICODE_STRING proc_name_U = {};
846  RtlInitUnicodeString(&proc_name_U, L"_vsnwprintf");
847  local__vsnwprintf = reinterpret_cast<_vsnwprintf_type *>(
848  MmGetSystemRoutineAddress(&proc_name_U));
849  }
850 
851  return local__vsnwprintf(_Buffer, _BufferCount, _Format, _ArgList);
852 }
853 
854 } // extern "C"
NTSTATUS LogInitialization(ULONG flag, const wchar_t *log_file_path)
Definition: log.cpp:159
volatile char * log_buffer_tail
Definition: log.cpp:54
static NTSTATUS LogpWriteMessageToFile(_In_z_ const char *message, _In_ const LogBufferInfo &info)
#define HYPERPLATFORM_LOG_INFO(format,...)
Definition: log.h:38
#define HYPERPLATFORM_LOG_DEBUG(format,...)
Logs a message as respective severity.
Definition: log.h:34
static void LogpDbgBreak()
Definition: log.cpp:802
_Check_return_opt_ int __cdecl __stdio_common_vsprintf(_In_ unsigned __int64 _Options, _Out_writes_opt_z_(_BufferCount) char *_Buffer, _In_ size_t _BufferCount, _In_z_ _Printf_format_string_params_(2) char const *_Format, _In_opt_ _locale_t _Locale, va_list _ArgList)
Definition: log.cpp:810
static const auto kLogpBufferSizeInPages
Definition: log.cpp:29
void LogTermination()
Terminates the log system. Should be called from a DriverUnload routine.
Definition: log.cpp:348
volatile bool buffer_flush_thread_started
Definition: log.cpp:67
static const auto kLogpLevelOptSafe
Save this log to buffer and not try to write to a log file.
Definition: log.h:82
static const auto kLogOptDisableFunctionName
For LogInitialization(). Do not log a current function name.
Definition: log.h:110
static const auto kLogpLogFlushIntervalMsec
Definition: log.cpp:39
static const auto kLogpBufferUsableSize
Definition: log.cpp:36
static bool LogpIsDbgPrintNeeded()
Definition: log.cpp:747
char * log_buffer2
Definition: log.cpp:57
static void LogpSetPrintedBit(_In_z_ char *message, _In_ bool on)
static DRIVER_REINITIALIZE LogpReinitializationRoutine
Definition: log.cpp:86
_Check_return_opt_ int __cdecl __stdio_common_vswprintf(_In_ unsigned __int64 _Options, _Out_writes_opt_z_(_BufferCount) wchar_t *_Buffer, _In_ size_t _BufferCount, _In_z_ _Printf_format_string_params_(2) wchar_t const *_Format, _In_opt_ _locale_t _Locale, va_list _ArgList)
Definition: log.cpp:832
static const auto kLogPutLevelDisable
For LogInitialization(). Disables all levels of logs.
Definition: log.h:104
static const auto kLogpLevelDebug
Bit mask for DEBUG level logs.
Definition: log.h:84
static void LogpDoDbgPrint(_In_z_ char *message)
static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &info)
static bool LogpIsLogFileActivated(_In_ const LogBufferInfo &info)
static bool LogpIsPrinted(_In_z_ char *message)
static const auto kLogOptDisableProcessorNumber
For LogInitialization(). Do not log a current processor number.
Definition: log.h:113
NTSTATUS LogpPrint(ULONG level, const char *function_name, const char *format,...)
Definition: log.cpp:397
char * log_buffer1
Definition: log.cpp:56
static const auto kLogpLevelInfo
Bit mask for INFO level logs.
Definition: log.h:85
static const auto kLogpLevelError
Bit mask for ERROR level logs.
Definition: log.h:87
volatile char * log_buffer_head
Definition: log.cpp:51
bool resource_initialized
Definition: log.cpp:65
static bool LogpIsLogNeeded(_In_ ULONG level)
NTKERNELAPI UCHAR *NTAPI PsGetProcessImageFileName(_In_ PEPROCESS process)
KSPIN_LOCK spin_lock
Definition: log.cpp:63
static NTSTATUS LogpMakePrefix(_In_ ULONG level, _In_z_ const char *function_name, _In_z_ const char *log_message, _Out_ char *log_buffer, _In_ SIZE_T log_buffer_length)
volatile bool buffer_flush_thread_should_be_alive
Definition: log.cpp:66
SIZE_T log_max_usage
Definition: log.cpp:60
static NTSTATUS LogpInitializeLogFile(_Inout_ LogBufferInfo *info)
static const ULONG kLogpPoolTag
Definition: log.cpp:41
void LogIrpShutdownHandler()
Terminates the log system. Should be called from an IRP_MJ_SHUTDOWN handler.
Definition: log.cpp:331
static const auto kLogpBufferSize
Definition: log.cpp:32
static const auto kLogOptDisableDbgPrint
For LogInitialization(). Do not log to debug buffer.
Definition: log.h:116
static NTSTATUS LogpBufferMessage(_In_z_ const char *message, _Inout_ LogBufferInfo *info)
HANDLE buffer_flush_thread_handle
Definition: log.cpp:68
static KSTART_ROUTINE LogpBufferFlushThreadRoutine
Definition: log.cpp:121
static NTSTATUS LogpPut(_In_z_ char *message, _In_ ULONG attribute)
static auto g_logp_debug_flag
Definition: log.cpp:150
ERESOURCE resource
Definition: log.cpp:64
void LogRegisterReinitialization(PDRIVER_OBJECT driver_object)
Definition: log.cpp:305
static const auto kLogOptDisableTime
For LogInitialization(). Do not log a current time.
Definition: log.h:107
static const auto kLogpLevelWarn
Bit mask for WARN level logs.
Definition: log.h:86
static LogBufferInfo g_logp_log_buffer_info
Definition: log.cpp:151
HANDLE log_file_handle
Definition: log.cpp:62
Declares interfaces to logging functions.
static const char * LogpFindBaseFunctionName(_In_z_ const char *function_name)
static NTSTATUS LogpFlushLogBuffer(_Inout_ LogBufferInfo *info)
wchar_t log_file_path[200]
Definition: log.cpp:69
static void LogpFinalizeBufferInfo(_In_ LogBufferInfo *info)
static NTSTATUS LogpSleep(_In_ LONG millisecond)
static NTSTATUS LogpInitializeBufferInfo(_In_ const wchar_t *log_file_path, _Inout_ LogBufferInfo *info)