sticky
HOWTO: Capture kernel stack traces RRS feed

  • Question

  • I had been wondering for a long time how to get kernel stack traces as Process Explorer does. I accidentally came across the function RtlCaptureStackBackTrace while browsing through some headers. Although this function only captures a stack trace for the current thread, we can use APCs to target a specific thread.

    Although I will not post full source code, here is the general idea:

    typedef VOID (*PKKERNEL_ROUTINE)(
        PKAPC Apc,
        PKNORMAL_ROUTINE *NormalRoutine,
        PVOID *NormalContext,
        PVOID *SystemArgument1,
        PVOID *SystemArgument2
        );

    typedef VOID (*PKRUNDOWN_ROUTINE)(
        PKAPC Apc
        );

    typedef VOID (*PKNORMAL_ROUTINE)(
        PVOID NormalContext,
        PVOID SystemArgument1,
        PVOID SystemArgument2
        );


    typedef enum _KAPC_ENVIRONMENT
    {
        OriginalApcEnvironment,
        AttachedApcEnvironment,
        CurrentApcEnvironment,
        InsertApcEnvironment
    } KAPC_ENVIRONMENT, *PKAPC_ENVIRONMENT;

    /* These two functions are actually exported by ntoskrnl. */
    NTKERNELAPI VOID KeInitializeApc(
        PKAPC Apc,
        PKTHREAD Thread,
        KAPC_ENVIRONMENT Environment,
        PKKERNEL_ROUTINE KernelRoutine,
        PKRUNDOWN_ROUTINE RundownRoutine,
        PKNORMAL_ROUTINE NormalRoutine,
        KPROCESSOR_MODE ProcessorMode,
        PVOID NormalContext
        );

    NTKERNELAPI BOOLEAN KeInsertQueueApc(
        PRKAPC Apc,
        PVOID SystemArgument1,
        PVOID SystemArgument2,
        KPRIORITY Increment
        );

    typedef struct _CAPTURE_BACKTRACE_THREAD_CONTEXT
    {
        BOOLEAN Local; /* Whether we are capturing the stack of the current thread. */
        KAPC Apc; /* Storage for the APC (not valid for local stack trace). */
        KEVENT CompletedEvent; /* The event that will be signaled when the stack trace has been captured in the remote thread. */
        ULONG FramesToSkip; /* The number of frames to skip. */
        ULONG FramesToCapture; /* The number of frames to capture. */
        PVOID *BackTrace; /* Storage for the back trace. */
        ULONG CapturedFrames; /* The number of captured frames. */
        ULONG BackTraceHash; /* A hash of the back trace. */
    } CAPTURE_BACKTRACE_THREAD_CONTEXT, *PCAPTURE_BACKTRACE_THREAD_CONTEXT;

    NTSTATUS CaptureStackTrace(
        PETHREAD Thread,
        ULONG FramesToSkip,
        ULONG FramesToCapture,
        PVOID *BackTrace,
        PULONG CapturedFrames,
        PULONG BackTraceHash,
        KPROCESSOR_MODE PreviousMode
        )
    {
        NTSTATUS status = STATUS_SUCCESS;
        CAPTURE_BACKTRACE_THREAD_CONTEXT context;
       
        /* Do some probing if this function is to be called from user-mode... */
       
        context.BackTrace = ExAllocatePoolWithTag(NonPagedPool, FramesToCapture * sizeof(PVOID), 'TSET'); /* supply your own tag */
        /* Check if the buffer was actually allocated... */
        /* Initialize the context structure. */
        context.FramesToSkip = FramesToSkip;
        context.FramesToCapture = FramesToCapture;
       
        /* Check if we are doing a local stack trace. */
        if (Thread = PsGetCurrentThread())
        {
            PCAPTURE_BACKTRACE_THREAD_CONTEXT contextPtr = &context;
            PVOID dummy = NULL;
            KIRQL oldIrql;
           
            context.Local = TRUE;
            /* Raise the IRQL to simulate an APC. */
            KeRaiseIrql(APC_LEVEL, &oldIrql);
            /* Call the APC routine directly. */
            CaptureStackTraceSpecialApc(
                &dummy,
                NULL,
                NULL,
                &contextPtr,
                &dummy
                );
            /* Restore the IRQL. */
            KeLowerIrql(oldIrql);
        }
        else
        {
            context.Local = FALSE;
            /* Initialize the event. */
            KeInitializeEvent(&context.CompletedEvent, NotificationEvent, FALSE);
            /* Initialize the APC. */
            KeInitializeApc(
                &context.Apc,
                (PKTHREAD)Thread, /* target the specified thread */
                OriginalApcEnvironment,
                CaptureStackTraceSpecialApc,
                NULL, /* no rundown routine */
                NULL, /* no normal routine */
                KernelMode,
                NULL, /* no normal context */
                );
            /* Queue the APC. */
            if (KeInsertQueueApc(
                &context.Apc,
                &context, /* pass the context in the first system argument */
                NULL, /* empty second system argument */
                2 /* boost priority by 2 */
                ))
            {
                /* Wait for the APC to complete. */
                status = KeWaitForSingleObject(
                    &context.CompletedEvent,
                    Executive,
                    KernelMode,
                    FALSE, /* no APCs */
                    NULL /* no timeout */
                    );
            }
            else
            {
                status = STATUS_UNSUCCESSFUL;
            }
        }
       
        if (NT_SUCCESS(status))
        {
            __try
            {
                /* Pass the information back. */
                memcpy(BackTrace, context.BackTrace, context.CapturedFrames * sizeof(PVOID));
                *CapturedFrames = context.CapturedFrames;
                *BackTraceHash = context.BackTraceHash;
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                status = GetExceptionCode();
            }
        }
       
        /* Free the buffer. */
        ExFreePoolWithTag(context.BackTrace, 'TSET');
       
        return status;
    }

    VOID CaptureStackTraceSpecialApc(
        PKAPC Apc,
        PKNORMAL_ROUTINE *NormalRoutine,
        PVOID *NormalContext,
        PVOID *SystemArgument1,
        PVOID *SystemArgument2
        )
    {
        PCAPTURE_BACKTRACE_THREAD_CONTEXT context = *SystemArgument1;
       
        /* Capture the stack trace. */
        context->CapturedFrames = RtlCaptureStackBackTrace(
            context->FramesToSkip,
            context->FramesToCapture,
            context->BackTrace,
            &context->BackTraceHash
            );
        /* Signal the completion event. */
        if (!context->Local)
            KeSetEvent(&context->CompletedEvent, 0, FALSE);
    }


    EDIT: fix typo at request of wj32
    Saturday, June 20, 2009 5:38 PM

All replies