A Device Driver for W2K Signals

Unix signals provide a simple IPC that is also asynchronous - your signal handler gets invoked when the signal arrives, no matter what your code was in the middle of. Achieving the same effect under Windows 2000 requires a device driver, so that you can make use of asynchronous procedure calls (APCs).


August 01, 2001
URL:http://www.drdobbs.com/a-device-driver-for-w2k-signals/184416344

August 2001/A Device Driver for W2K Signals


One major difference between Windows and Unix-based operating systems is the support for handling programmer-defined signals. Although the Standard C library provides some basic support for signal handling [2], it is not enough for the programmer who needs signals mainly for implementing interprocess communication. Indeed, the absence of such a mechanism in the context of Windows makes the asynchronous communication between the processes (threads) difficult and requires the usage of special data structures, like events, and the creation of dedicated threads that poll continuously the status of some conditions [6]. In this artice, I introduce SignalsLib, a library for the support of signal handling on Win32 programming platforms. The heart of the library is a device driver that provides the mechanisms needed to cause a signal handler in a target process to execute asynchronously, even if the target process is not in an alertable state.

This article provides a general overview of signal handling, an outline of the support Windows provides for signals and some basic mechanisms, the design and implementation of the library and driver, performance measurements of the implemented mechanisms, an API that is exported to the programmer, and possible uses and extensions of this work.

Overview of Signals

Signals are a mechanism for informing processes about asynchronous events. The basic idea is that each distinct signal has an associated integer code, and any process can register a handler (a callback function) for any signal (by specifying its integer code). When one process sends a specific signal to another process that has registered a handler for that signal, the target process will stop whatever it is doing and execute the handler function registered for that signal.

The signal mechanism is analogous to interrupt handling, in that a signal interrupts whatever code is currently executing in the target process. Just like interrupt handlers, signal handlers require careful coding — ordinary code must not access the same data as signal handlers unless they both use synchronization primitives to avoid corrupting each other’s work. Signals provide simple and convenient interprocess communication. Common traditional uses include: notifying a service that it should rotate its log files, notifying a parent process that the child process has completed initialization and is ready to start doing useful work, notifying a process that it should temporarily pause operation, notifying a process that it should perform a clean shutdown as soon as possible, and so on.

Each signal is associated with an action that will be executed by the kernel on behalf of the process that has received the signal. For most signals, the default action is the termination of the process, although a process can require some alternative action from the system. The various possible alternatives are:

1. Ignore the signal. In this case, the process is not informed about the signal.

2. Restore the default signal action.

3. Execute a specific signal-handling function. This is the case in which the process wants to perform some custom action when a specific signal arrives. The process registers a special function that gets invoked asynchronously when the associated signal occurs. After the signal handling function returns, whatever code it interrupted will resume executing.

Windows Support for Signals

Win32 offers a very specific kind of signal support in the form of SetConsoleCtrlHandler(). This function lets a console process catch a variety of system-generated signals (the user pressed Ctrl-C, the user is logging off, etc.). It does not offer any programmer-defined signals, nor does it offer any interprocess communication — it is strictly a means for the operating system to notify a process of a few specific events.

The only other signal-like mechanism that Windows offers is structured exception handling. However, Standard C requires support for the well-known signal()/raise() Unix functions and a constrained number of signals [2]. signal() sets the signal-handling function to be called for a given signal. raise() sends a specific signal to the current process, invoking either whatever handler was registered for that signal, or else the default action associated with that signal. Again, these signals are not extensible, and they are local to a given process. (Standard C defines no function for sending a signal to another process.)

Instead of signals, Windows supports APCs (Asynchronous Procedure Calls). An APC is a kernel-defined control object that represents a procedure that is called asynchronously. APCs have the following features [7]:

1. An APC always runs in a specific thread context.

2. An APC runs at OS predetermined times.

3. APCs can preempt the currently running thread.

4. APC routines can themselves be preempted.

There are three different types of APCs in the kernel [2,3]:

User-mode APCs. User-mode APCs are, by default, disabled; that is, they are queued to the user-mode thread, but are not executed, except at well-defined points in the program. Specifically, they can be executed only when an application has either called a wait service and has enabled alerts to occur or, called the test-alert service.

Normal kernel-mode APCs. These are much like user-mode APCs, except that they are executable by default. That is, they are enabled except when the thread is already executing a kernel-mode APC or is inside a critical code section.

Special kernel-mode APCs. These cannot be blocked, except by running at a raised IRQL (interrupt request level). Special kernel-mode APCs are executed at IRQL APC_LEVEL, in kernel mode. They are used by the system to force a thread to execute a procedure in a thread’s context. A special kernel-mode APC can preempt the execution of a normal kernel-mode APC.

The Win32 API [4] provides QueueUserAPC(), which allows an application to queue an APC object to a thread. The queuing of an APC is a request for the thread to call the APC function. When a user-mode APC is queued, the thread is not directed to call the APC function unless it is in an alertable state. Unfortunately, a thread enters an alertable state only by using one of the following Win32 API functions: SleepEx(), SignalObjectAndWait(), WaitForSingleObjectEx(), WaitForMultipleObjectEx(), or MsgWaitForMultipleObjectsEx().

In kernel mode [1], the programmer initializes an APC object using KeInitializeApc(), defining the target thread, one kernel-mode and one user-mode callback routine, the type of the APC (kernel or user), and finally an argument to these two functions. Next, the APC is queued (KeInsertQueueApc()) to the target thread and can be executed without having the thread be in an alertable state.

The SignalsLib Interface

The library provides the necessary structures and mechanisms for elementary support of signal handling exporting an appropriate interface to the programmer. During the building process, the user can define the option for support per thread or the usage of a global table of signal handlers.

signals.h (Listing 1) defines the available signals, from zero to MAX_SIGNALS-1. This header file also defines the two functions that make up the interface to the signals library.

SetSignalHandler() sets a handler for a given signal. It returns zero for failure, or non-zero for success.

SendSignalToThread() sends a signal to the specified thread. You must specify the handle of the thread you want to signal, as well as the signal (integer code) you want to send. It returns zero for failure, or non-zero for success.

testapp.c (Listing 2) demonstrates how to use the signals library. This application creates a thread that sets a handler for a specific signal, and the main thread then sends that signal to the thread, resulting in the execution of the installed signal handler. Of course, the driver must be installed into the system and loaded, or else an appropriate message is printed, when the library’s DLLMain() is executed.

Design and Implementation

SignalsLib consists of a DLL and a kernel-mode device driver. The DLL provides a user-mode interface for applications, while the device driver is necessary for accessing the kernel-mode functions used to queue a kernel-mode APC to the target thread. The calling application simply makes calls to SetSignalHandler() and SendSignalToThread(), however — the DLL hides all the details of communicating with the device driver.

SetSignalHandler() is simple — it just stores the supplied function pointer in the appropriate position of a global array of signal handlers. When a signal actually occurs, the internal routine SignalsDriverRoutine() will get invoked and use this global array to determine what signal handler to call. Both of these routines are in signals.c (Listing 3).

SendSignalToThread() is where the DLL communicates with the device driver. DllMain() obtains a handle for the device driver when the DLL is first loaded and releases that handle when the DLL is unloaded. SendSignalToThread() uses that handle in a call to DeviceIoControl() to pass the device driver a SIGINFO structure:

typedef struct _SIGINFO
{
   HANDLE   hThread;   /* target thread */
   ULONG    SigNo;     /* signal number */
   ULONG    SigFunc;   /* address of DriverRoutine */
} SIGINFO, *PSIGINFO;

Note that SigFunc is not the address of an individual signal handler, but rather the address of SignalsDriverRoutine(), a function in the DLL that looks up and invokes the correct signal handler.

When SendSignalToThread() passes this information to DeviceIoControl(), it will cause the driver’s interrupt service routine to be invoked. The main source code for the driver is in sigdrv.c (Listing 4). The driver’s interrupt service routine invokes SigDriverSendTheSignal(), which is responsible for queuing an appropriate kernel-mode APC to the target thread. SigDriverSendTheSignal() takes a pointer to the ETHREAD data structure [2] of the target thread. It calls KeInitializeApc() to initialize a kernel-mode APC and then calls KeInsertQueueApc() to queue that APC for the target thread.

The queued APC contains a pointer to another function in sigdrv.c (Listing 4): UserApcCallBack(). This function will get invoked in user mode and passed the SIGINFO structure. UserApcCallBack() uses the information in SIGINFO to invoke the DLL function SignalsDriverRoutine(), which will in turn look up and invoke the signal handler associated with the given signal.

Performance Evaluation

The choice of normal rather than special kernel-mode APCs has to do with the desired functionality, not with performance. If it’s important that the signal handler be preempted if another signal occurs, then you should use special kernel-mode APCs rather than normal kernel-mode APCs.

The APC mechanism performs extremely well; the signal handler is invoked as soon as the target thread is scheduled, typically within a few microseconds. It is important to mention that kernel-mode APCs achieve immediate signal delivery, independently of the system load (number of threads that run on the system). You might be able to reduce the response time further via thread priorities. For example, SendSignalToThread() could increase the priority of the target thread.

Conclusions

I’ve implemented the basic mechanisms of user-defined signals, mainly in order to enable asynchronous communication between threads of the same or different Win32 applications. The resulting DLL and device driver combination provides the two major functions for signal handling, similar to the signal() and kill() Unix system calls, and for signals similar to SIGUSR1 and SIGUSR2.

A possible extension of this library includes the implementation of the handlers for some Unix signals, like SIGSTOP, SIGCONT, and SIGTERM, and the support of the POSIX standard. It would also be interesting to integrate these mechanisms with the Standard C library. The library assists in implementing the pthread_kill() POSIX function and makes easier the development of applications that require notification mechanisms between their threads in user mode or from kernel to user mode, as the device driver needs only to know the address of the driver routine. Although the POSIX standard defines global signal handlers for all the threads of an application, the library can easily support per-thread signal handlers by using thread local storage; for simplicity, that was not done in this version.

To keep the code simple for publication, this implementation assumes that all participating processes will share a single instance of signals.dll. More specifically, SendSignalToThread() always passes the address of SignalsDriverRoutine() (in the context of the calling process) to the device driver, but the device driver will attempt to use that address in the context of the target process (which will likely be different). That will be disastrous if the target process loaded signals.dll at a different address than the calling process did. If a particular process cannot load signals.dll at its default address, you can always select another default address until you find one that does not collide with anything already loaded. You could solve the problem more elegantly by revising the interface so that the device driver has enough information to locate the correct callback address for a given thread.

Finally, an alternative implementation, which is also not presented here, is based on user-mode APCs. SendSignalToThread() uses the Win32’s QueueUserAPC() and next, through a kernel-mode device driver, manages to set the target thread in alertable state. This is possible by setting to one the memory address that corresponds to 0x4a bytes offset [5] from the base address of the ETHREAD data structure [2].

References

[1] E. N. Dekker and J. M. Newcomer. Developing Windows NT Device Drivers: A Programmer’s Handbook (Addison-Wesley, 1999).

[2] Microsoft Corporation. Microsoft Developer Network Library, msdn.microsoft.com/library.

[3] D. A. Solomon. Inside Windows NT, Second Edition (Microsoft Press, 1998).

[4] J. Ritcher. Advanced Windows: The Professional Developer’s Guide to the Win32 API for Windows NT 4.0 and Windows 95 (Microsoft Press, 1995).

[5] www.cmkrnl.com/arc-userapc.html

[6] www.microsoft.com/msj/0799/nerd/nerd0709.html

[7] www.osr.com/insider/1998/apc.html

[8] Microsoft Visual Studio\VC98\CRT\SRC\WINSIG.C

Panagiotis Hadjidoukas is a postgraduate student at High Performance Information Systems Laboratory, Department of Computer Engineering & Informatics, at the University of Patras in Greece. You can reach him by email at [email protected] or at the web page of his laboratory at http://www.hpclab.ceid.upatras.gr.

August 2001/A Device Driver for W2K Signals/Listing 1

Listing 1: signals.h — Interface to signal library

/* Number of supported signals */
#define MAX_SIGNALS     4

/* Signal names */
#define SIGNAL0 0
#define SIGNAL1 1
#define SIGNAL2 2
#define SIGNAL3 3

#ifndef SIGNALSLIBAPI
#define SIGNALSLIBAPI __declspec(dllimport)
#endif

/* Set a signal handler */
SIGNALSLIBAPI DWORD SetSignalHandler(DWORD, PVOID);

/* Send a signal to a thread */
SIGNALSLIBAPI DWORD SendSignalToThread(HANDLE, DWORD);
/* End of File */
August 2001/A Device Driver for W2K Signals/Listing 2

Listing 2: testapp.c — Source for signal demonstration program

#define STRICT 1
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "..\dll\signals.h"

/* Global variables to avoid any optimization by the compiler */
long sum1 = 0, sum2 = 0;

volatile unsigned long WaitFlag = 0;

VOID __stdcall fSignal0(VOID)
{
   printf("Thread (%ld):Inside the handler \
of signal SIGNAL0!!!\n", GetCurrentThreadId());
   return;
}

DWORD WINAPI ThreadRoutine(LPVOID Param)
{
   long i;

   /* Set a hanndler for SIGNAL0 */
   SetSignalHandler(SIGNAL0, fSignal0);

   /* Do some stuff.... */
   for (i = 0; i < 10000000; i++)  sum1++;

   /* Wait until the main thread sets the flag */
   while (WaitFlag == 0);
   return 0;
}

HANDLE hThread;
DWORD ThreadId;

int main()
{
   ULONG   i = 0;
   DWORD   Data = 1;   /* Not actually used */

   /* Create the target thread */
   hThread = CreateThread(NULL,0,ThreadRoutine,&Data,0,&ThreadId);

   /* Let the target thread to run for a while */
   Sleep(1000);

   /* Send a signal to the target thread */
   printf("Thread (%ld): Sends a signal to the \
target thread\n", GetCurrentThreadId());
   SendSignalToThread(hThread, SIGNAL0);

   /* Do some stuff and wait for a while */
   for (i = 0; i < 10000000; i++)  sum2++;
   Sleep(1000); 

   /* Set the flag. The handler must have been executed by now */
   WaitFlag = 1;

   /* Wait for the thread's termination */
   WaitForSingleObject(hThread, INFINITE);

   return 0;
}
/* End of File */
August 2001/A Device Driver for W2K Signals/Listing 3

Listing 3: signals.c — Source for signal DLL

#define STRICT 1
#include <windows.h>
#include <stdio.h>
#include <winioctl.h>
#include <string.h>
#include <stdlib.h>

#include "..\driver\sigdrv.h"
#define SIGNALSLIBAPI __declspec(dllexport)
#include "signals.h"

/* Global array of signal handler */
VOID (__stdcall *functable[MAX_SIGNALS])(VOID);

/* Handle to the device driver */
HANDLE hDevice;

/* Set a signal thread */
DWORD SetSignalHandler(DWORD SignalNumber, PVOID f)
{
   if (SignalNumber < MAX_SIGNALS)
   {
      functable[SignalNumber] = f;
      return 0;
   }
   return 1;
}

/* Call the appropriate signal handler */
DWORD WINAPI SignalsDriverRoutine(DWORD SignalNumber)
{
   if (functable[SignalNumber] != NULL)
      (functable[SignalNumber])();
   else
      printf("NULL signal function...!\n");

   return 0;
}


/* this information is sent to the device driver */
typedef struct _SIGINFO
{
   HANDLE   hThread;   /* target thread */
   ULONG    SigNo;     /* signal number */
   ULONG    SigFunc;   /* address of DriverRoutine */
} SIGINFO, *PSIGINFO;

SIGINFO     siginfo;

/* Send a signal to the targer thread */
DWORD SendSignalToThread(HANDLE hThread, DWORD SignalNumber)
{
   DWORD   cbReturned;

   /* Initialize a SIGINFO structure */
   siginfo.hThread = hThread;
   siginfo.SigNo = SignalNumber;
   siginfo.SigFunc = (unsigned long)SignalsDriverRoutine;

   /* Send the information to the driver */
   if (!DeviceIoControl (hDevice,
                         (DWORD)IOCTL_SIGDRV_SEND_SIGNAL,
                         (PSIGINFO) &siginfo,
                         sizeof(SIGINFO),
                         NULL,
                         0,
                         &cbReturned,
                         0
                         ) )
   {
      return 1;
   }
    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hDllInst, DWORD fdwReason,
                    LPVOID lpvReserved)
{
   switch (fdwReason)
   {
      case DLL_PROCESS_ATTACH:
         if ((hDevice =
                CreateFile(
                  "\\\\.\\Global\\SIGDRV",
                  GENERIC_READ | GENERIC_WRITE,
                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  NULL
             )) == INVALID_HANDLE_VALUE)
         {
             printf ("Can't get a handle to the driver\n");
             return FALSE;
         }

         break;
      case DLL_PROCESS_DETACH:
           CloseHandle(hDevice);
         break;
      case DLL_THREAD_ATTACH:
         break;
      case DLL_THREAD_DETACH:
         break;
   }
   return TRUE;
}
 
/* End of File */
August 2001/A Device Driver for W2K Signals/Listing 4

Listing 4: sigdrv.c — Kernel-mode driver for signal library

#define STRICT 1
#include "ntddk.h"
#include "string.h"
#include "sigdrv.h"

#define SIGDRV_DEVICE_NAME_U     L"\\Device\\Sigdrv"
#define SIGDRV_DOS_DEVICE_NAME_U L"\\DosDevices\\SIGDRV"

//  Debugging macros
#ifdef DBG
#define SigDrvKdPrint(_x_) \
                DbgPrint("SigDrv.sys: ");\
                DbgPrint _x_;
#else
#define SigDrvKdPrint(_x_)
#endif

NTSTATUS DriverEntry(
   IN PDRIVER_OBJECT  DriverObject,
   IN PUNICODE_STRING registryPath);

VOID SigDrvUnload(
   IN PDRIVER_OBJECT DriverObject);

NTSTATUS SigDrvDispatch(
   IN PDEVICE_OBJECT DeviceObject,
   IN PIRP Irp);

NTSTATUS SigDrvSendTheSignal(
   IN PDEVICE_OBJECT DeviceObject,
   IN OUT PVOID ioBuffer,
   IN ULONG inputBufferLength,
   IN ULONG outputBufferLength);

void KeInitializeApc(
   PKAPC Apc,
   PKTHREAD Thread,

   CCHAR ApcStateIndex,
   PKKERNEL_ROUTINE KernelRoutine,
   PKRUNDOWN_ROUTINE RundownRoutine,
   PKNORMAL_ROUTINE NormalRoutine,
   KPROCESSOR_MODE ApcMode,
   PVOID NormalContext);


void KeInsertQueueApc(
   PKAPC Apc,
   PVOID SystemArgument1,
   PVOID SystemArgument2,
   UCHAR unknown);

// Information the driver receives from user mode
typedef struct _SIGINFO
{
   HANDLE   hThread;   // handle of targer thread
   ULONG    SigNo;     // which signal
   ULONG    SigFunc;   // signals' driver-routine of the dll
} SIGINFO, *PSIGINFO;

void KernelApcCallBack(
   PKAPC Apc,
   PKNORMAL_ROUTINE NormalRoutine,
   PVOID NormalContext,
   PVOID SystemArgument1,
   PVOID SystemArgument2)
{
   ExFreePool(Apc);    // just free the kernel memory
   return;
}

void UserApcCallBack(PVOID arg1, PVOID arg2, PVOID arg3)
{
   PSIGINFO psiginfo = (PSIGINFO) arg3;
   ULONG (*SignalDriverRoutine)(ULONG);

   // take the user mode address of the function
   SignalDriverRoutine = (unsigned long (__stdcall *)
        (unsigned long)) psiginfo->SigFunc;

   // call the driver-routine
   SignalDriverRoutine(psiginfo->SigNo);

   return;
}

NTSTATUS DriverEntry(
   IN PDRIVER_OBJECT DriverObject,
   IN PUNICODE_STRING RegistryPath)
{
   PDEVICE_OBJECT deviceObject=NULL;
   NTSTATUS       ntStatus;
   WCHAR          deviceNameBuffer[]=SIGDRV_DEVICE_NAME_U;
   UNICODE_STRING deviceNameUnicodeString;
   WCHAR          deviceLinkBuffer[]=SIGDRV_DOS_DEVICE_NAME_U;
   UNICODE_STRING deviceLinkUnicodeString;

   RtlInitUnicodeString (&deviceNameUnicodeString,
                         deviceNameBuffer);
   ntStatus = IoCreateDevice (
                      DriverObject,0,&deviceNameUnicodeString,
                      FILE_DEVICE_SIGDRV,0,FALSE,&deviceObject);
   if (!NT_SUCCESS(ntStatus))
   {
       SigDrvKdPrint(("IoCreateDevice failed:%x\n", ntStatus));
       return ntStatus;
   }

   DriverObject->MajorFunction[IRP_MJ_CREATE] =
   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
   DriverObject->MajorFunction[IRP_MJ_CLOSE]  = SigDrvDispatch;

   DriverObject->DriverUnload = SigDrvUnload;

   RtlInitUnicodeString (&deviceLinkUnicodeString,
                         deviceLinkBuffer);

   ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                    &deviceNameUnicodeString);
   if (!NT_SUCCESS(ntStatus))
   {
      SigDrvKdPrint (("IoCreateSymbolicLink failed\n"));
      IoDeleteDevice (deviceObject);
   }

   return ntStatus;
}

NTSTATUS SigDrvDispatch(
   IN PDEVICE_OBJECT DeviceObject,
   IN PIRP Irp)
{
   PIO_STACK_LOCATION irpStack;
   PVOID              ioBuffer;
   ULONG              inputBufferLength;
   ULONG              outputBufferLength;
   ULONG              ioControlCode;
   NTSTATUS           ntStatus;

   Irp->IoStatus.Status      = STATUS_SUCCESS;
   Irp->IoStatus.Information = 0;

   irpStack = IoGetCurrentIrpStackLocation(Irp);

   ioBuffer           = Irp->AssociatedIrp.SystemBuffer;
   inputBufferLength  =
      irpStack->Parameters.DeviceIoControl.InputBufferLength;
   outputBufferLength =
      irpStack->Parameters.DeviceIoControl.OutputBufferLength;

   switch (irpStack->MajorFunction)
   {
      case IRP_MJ_CREATE:
         SigDrvKdPrint (("IRP_MJ_CREATE\n"));
         break;

      case IRP_MJ_CLOSE:
         SigDrvKdPrint (("IRP_MJ_CLOSE\n"));
         break;

      case IRP_MJ_DEVICE_CONTROL:
         ioControlCode =
           irpStack->Parameters.DeviceIoControl.IoControlCode;

         switch (ioControlCode)
         {
            case IOCTL_SIGDRV_SEND_SIGNAL:
               Irp->IoStatus.Status = SigDrvSendTheSignal(
                                          DeviceObject,
                                          ioBuffer,
                                          inputBufferLength,
                                          outputBufferLength);

               if (NT_SUCCESS(Irp->IoStatus.Status))
               {
                  Irp->IoStatus.Information = sizeof(PVOID);
                  SigDrvKdPrint(("Signal was sent\n"));
               }
               else
               {
                  Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
                  SigDrvKdPrint(("Signal failed to be sent\n"));
               }
               break;
            default:
               SigDrvKdPrint (("unknown IRP_MJ_DEVICE_CONTROL\n"));
               Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
               break;
         }
         break;
   }

   ntStatus = Irp->IoStatus.Status;
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return ntStatus;
}

VOID SigDrvUnload(IN PDRIVER_OBJECT DriverObject)
{
   WCHAR deviceLinkBuffer[]  = SIGDRV_DOS_DEVICE_NAME_U;
   UNICODE_STRING deviceLinkUnicodeString;

   RtlInitUnicodeString (&deviceLinkUnicodeString,
                             deviceLinkBuffer);
   IoDeleteSymbolicLink (&deviceLinkUnicodeString);
   IoDeleteDevice (DriverObject->DeviceObject);

   return;
}

NTSTATUS SigDrvSendTheSignal(
  IN PDEVICE_OBJECT DeviceObject,
  IN OUT PVOID      IoBuffer,
  IN ULONG          InputBufferLength,
  IN ULONG          OutputBufferLength)
{
   NTSTATUS   ntStatus = STATUS_SUCCESS;
   PVOID      virtualAddress;
   SIGINFO    *psiginfo = (PSIGINFO) IoBuffer;
   PETHREAD   uThread = NULL;
   PKAPC      kApc;

   // take a pointer to the kernel thread structure
   ntStatus = ObReferenceObjectByHandle(
                psiginfo->hThread, THREAD_ALL_ACCESS,
                NULL, KernelMode, &uThread, NULL);
   if (NT_ERROR(ntStatus)) {
      SigDrvKdPrint (("ObReferenceObjectByHandle Failed\n"));
      return ntStatus;
   }

   // Allocate an KAPC structure from NonPagedPool
   kApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));

   KeInitializeApc(kApc,
                   (PKTHREAD) uThread, 0,
                   (PKKERNEL_ROUTINE) &KernelApcCallBack, 0,
                   (PKNORMAL_ROUTINE) &UserApcCallBack,
                   KernelMode, (PVOID) 0);
   KeInsertQueueApc (kApc, (PVOID) (ULONG) 10, (PVOID) psiginfo, 0);

   ObDereferenceObject((PVOID) uThread);
   return ntStatus;
}
/* End of File */

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.