Geoff Chappell, Software Analyst
SKETCH OF HOW RESEARCH MIGHT CONTINUE AND RESULTS BE PRESENTED
The HAL calls this function when a profile interrupt occurs. It is the kernel’s opportunity to do such things as update execution counts for profiling.
VOID KeProfileInterruptWithSource ( KTRAP_FRAME *TrapFrame, KPROFILE_SOURCE ProfileSource);
The TrapFrame argument describes the interrupted execution.
The ProfileSource argument tells what caused the interrupt.
The KeProfileInterruptWithSource function is exported from the kernel in version 3.51 and higher.
The KeProfileInterruptWithSource function is not documented, but has a C-language declaration in the NTOSP.H file in the Windows Driver Kit (WDK) for Windows 10.
The following implementation notes are from inspection of the kernel from the original release of Windows 10 only. They may some day get revised to account for earlier versions. Meanwhile, where anything is added about earlier versions, take it not as an attempt at comprehensiveness but as a bonus from my being unable to resist a trip down memory lane or at least a quick look into the history.
The 32-bit implementation increments the InterruptCount in the current processor’s KPRCB. This count is also incremented for other interrupts, of course. That the 32-bit kernel increments it specially for the profile interrupt has to do with the very different ways that the kernel and HAL cooperate over interrupt handling in 32-bit and 64-bit Windows (which is its own, huge, topic far beyond the scope of this note).
However the count gets incremented for any interrupt, it is the same InterruptCount that can be retrieved for each processor via the ZwQuerySystemInformation and ZwQuerySystemInformationEx functions when given the information class SystemProcessorPerformanceInformation (0x08) or SystemProcessorPerformanceInformationEx (0x8D).
The function’s main work is to process two lists of profile objects. Each profile object carries conditions for triggering and parameters for what’s to be done when triggered. Among the possible conditions is that profiling can be specific to a process or may apply globally. The kernel keeps separate lists for each process, the head being the ProcessListHead very near the start of the KPROCESS), and one more list for global profiling. This function processes just the list for the current process and the global list.
Profile objects come in two types. Both types can appear in any list. In practice, however, one type is only ever inserted into the global list. Both types of profile object have the same structure, Microsoft’s name for which is not known. As with other kernel objects, the structure for profile objects begins with a type from the KOBJECTS enumeration and a size.
In the basic age-old profile object, the type is simply ProfileObject (0x17, but 0x0F in version 3.51). This type of profile object is created by the NtStartProfile function using parameters that are first presented to NtCreateProfile or NtCreateProfileEx. If a process was specified among those parameters, then the profile object will be in the list for that process, and thus is examined by this function only if the interrupt occurs while executing that process. Otherwise, the profile object is in the global list. Whichever list it’s in, a profile object of this type triggers if all the following are true:
The profiled area is treated as an array of buckets whose size is specified for the profile object. Also specified for the profile object is a buffer that has space for one 32-bit counter per bucket. The function computes which bucket holds the instruction pointer and increments the corresponding counter. The intention is that after numerous calls to this function, the buffer is left with a useful profile of execution that fits the specified conditions.
The function’s notion of the profiled area’s end is problematic. In all versions, the end is simply and naturally ProfileSize bytes added to the ProfileBase address (in terms of arguments to NtCreateProfile and NtCreateProfileEx). A recoding for version 6.2 made the end inclusive instead of non-inclusive. Details and implications will be presented once Microsoft has sufficient opportunity to appreciate the problem and correct it.
The second type of profile object dates from version 6.2, i.e., from Windows 8. It seems to have been introduced to tidy what had been a growing assortment of odd jobs. Each now is represented by a profile callback object. This has a different type at the start, specifically ProfileCallbackObject (0x11). Where the basic profile object has start and end addresses for a profiled range of address space, the profile callback object has the address of a kernel-supplied callback function and arbitrary context. The action to be taken for this type of profile object is, of course, to call the callback function. It gets the TrapFrame and the context. The only trigger is that the ProfileSource argument must be the same as specified for the profile object.
Especially notable among these profile callback objects is one for which the callback function traces each profile interrupt to whichever NT Kernel Logger sessions have enabled it. The trace shows as an event whose hook ID is PERFINFO_LOG_TYPE_SAMPLED_PROFILE (0x0F2E). The event data, as a PERFINFO_SAMPLED_PROFILE_INFORMATION structure, includes not only the address where execution was interrupted but also a summary of whether the interrupted execution was itself an interruption (for an interrupt service routine or deferred procedure call).