Sysinternals Homepage
Forum Home Forum Home > Windows Discussions > Development
  New Posts New Posts RSS Feed - HOWTO: Capture kernel stack traces
  FAQ FAQ  Forum Search   Events   Register Register  Login Login

Topic ClosedHOWTO: Capture kernel stack traces

 Post Reply Post Reply
Author
Message
wj32 View Drop Down
Senior Member
Senior Member
Avatar

Joined: 16 January 2009
Location: Australia
Status: Offline
Points: 1016
Direct Link To This Post Topic: HOWTO: Capture kernel stack traces
    Posted: 21 June 2009 at 1:38am
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


Edited by molotov - 21 July 2009 at 11:27am
PH, a free and open source process viewer.
Back to Top
molotov View Drop Down
Moderator Group
Moderator Group
Avatar

Joined: 04 October 2006
Status: Offline
Points: 17531
Direct Link To This Post Posted: 24 June 2009 at 11:32am
Please discuss this topic here:
Discussion: HOWTO: Capture kernel stack traces
Daily affirmation:
net helpmsg 4006
Back to Top
 Post Reply Post Reply
  Share Topic   

Forum Jump Forum Permissions View Drop Down

Forum Software by Web Wiz Forums® version 11.06
Copyright ©2001-2016 Web Wiz Ltd.