2014年8月5日星期二

打印栈帧的一点信息

#include <ntifs.h>

LARGE_INTEGER  Cookie;


/*
功能:打印到现在为止的函数调用的栈帧的信息(栈回溯),而不是最终的函数调用栈帧的信息。

记得以前有个函数,可以打印函数调用堆栈的信息,如果有符号文件还可以显示更多的信息。
当时觉得没有用,一心想解析符号文件的信息。

现在想来,即使没有符号文件及相关的信息,有这些调用堆栈的信息,对开发这和解决问题也是有用的。
不能光想着符号文件。没有符号文件分析程序更加锻炼人的能力。

此文收集并简单测试一些信息。

更多的API如下:
IoGetStackLimits
IoGetInitialStack 
IoGetRemainingStackSize 
IoWithinStackLimits
KeExpandKernelStackAndCallout 
KeSetKernelStackSwapEnable

http://www.cnblogs.com/welfear/archive/2010/11/16/1878503.html
http://blog.csdn.net/cosmoslife/article/details/7818841
http://blogs.msdn.com/b/vcblog/archive/2014/01/23/examining-stack-traces-of-objects-using-visual-studio-2013.aspx
CaptureStackBackTrace 
RtlCaptureStackBackTrace 
RtlCaptureStackContext
RtlGetCallerAddress XP 已经导出。
RtlWalkFrameChain http://msdn.microsoft.com/zh-cn/office/ff563638(v=vs.100).aspx
KeExpandKernelStackAndCallout

made by correy
made at 2014.08.05
homepage:http://correy.webs.com
*/


void get_open_information_from_stack_in_32()
    /*
    相信也可以处理64位系统的,尽管它的参数的调用方式特殊,但参数也是存在栈上的。
    */
{
    /*
    The RtlCaptureStackBackTrace routine captures a stack back trace by walking up the stack and recording the information for each frame.
    Important  This is an exported function that MUST probe the ability to take page faults.
    所以:IRQL: <= DISPATCH_LEVEL

    Requirements
    Versions: Available in Windows XP and later versions of the Windows operating systems.
    */

    /*
    此时栈的信息类似如下:
    3: kd> k
    ChildEBP RetAddr  
    efaf6964 f8870132 test!get_open_information_from_stack_in_32+0x8 [e:\code\driver\test\test.c @ 20]
    efaf6970 f887005d test!post_open_key+0x12 [e:\code\driver\test\test.c @ 58]
    efaf6980 8056bdd4 test!RegistryCallback+0x1d [e:\code\driver\test\test.c @ 74]
    efaf69b4 806312bd nt!CmpCallCallBacks+0x50
    efaf6b88 805c0099 nt!CmpParseKey+0x7b9
    efaf6c00 805bca48 nt!ObpLookupObjectName+0x119
    efaf6c54 80626810 nt!ObOpenObjectByName+0xea
    efaf6d50 805427e8 nt!NtOpenKey+0x1c8
    efaf6d50 7c92e514 nt!KiSystemServicePostCall
    0168f700 7c92d5da ntdll!KiFastSystemCallRet
    0168f7e8 7c93019b ntdll!ZwOpenKey+0xc
    0168fa14 00000000 ntdll!RtlAllocateHeap+0x1c2
    在这种情况下是一定能找到:nt!NtOpenKey函数的地址的。
    */

    PVOID pbacts = IoGetInitialStack();//returns the base address of the current thread's stack.
    /*
    上面一行运行的结果如下:
    3: kd> r
    eax=efaf7000 ebx=8055b3a0 ecx=efaf6b44 edx=e1a02d90 esi=e1a02d88 edi=e1cf5508
    eip=f88700b1 esp=efaf6948 ebp=efaf6964 iopl=0         nv up ei ng nz ac pe nc
    cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000296
    test!get_open_information_from_stack_in_32+0x11:
    f88700b1 33c0            xor     eax,eax
    3: kd> dd efaf6948
    efaf6948  efaf7000 00000001 805f2501 80630424
    efaf6958  00000000 e16d2518 e1d1ab98 efaf6970
    efaf6968  f8870132 00000000 efaf6980 f887005d
    efaf6978  efaf6b44 0000000d efaf69b4 8056bdd4
    efaf6988  00000000 0000000d efaf6b44 00000000
    efaf6998  e16d2518 e1d1ab98 e1a02d90 e1a02d90
    efaf69a8  81ed9da0 8055b214 00000000 efaf6b88
    efaf69b8  806312bd 0000000d efaf6b44 e1bb5558
    3: kd> dd pbacts
    efaf6948  efaf7000 00000001 805f2501 80630424
    efaf6958  00000000 e16d2518 e1d1ab98 efaf6970
    efaf6968  f8870132 00000000 efaf6980 f887005d
    efaf6978  efaf6b44 0000000d efaf69b4 8056bdd4
    efaf6988  00000000 0000000d efaf6b44 00000000
    efaf6998  e16d2518 e1d1ab98 e1a02d90 e1a02d90
    efaf69a8  81ed9da0 8055b214 00000000 efaf6b88
    efaf69b8  806312bd 0000000d efaf6b44 e1bb5558
    可见IoGetInitialStack的内容和栈指针(这里是ESP)的内容的内容是一样的。
    */
    
    USHORT ncf = 0;//The number of captured frames
    USHORT i = 0;

    //In Windows XP and Windows Server 2003, the sum of the FramesToSkip and FramesToCapture parameters must be less than 63.
    ULONG FramesToSkip = 0;//The number of frames to skip from the start of the back trace. 
    ULONG FramesToCapture = 62;//The number of frames to be captured. 
    
    PVOID  * BackTrace = 0;//An array of pointers captured from the current stack trace. 
    PVOID  * pBackTrace = 0;

    /*An optional value that can be used to organize hash tables. 
    If this parameter is NULL, no hash value is computed.
    This value is calculated based on the values of the pointers returned in the BackTrace array.
    Two identical stack traces will generate identical hash values.
    */
    ULONG BackTraceHash = 0;

    /*
    http://msdn.microsoft.com/zh-cn/office/ff563638(v=vs.100).aspx
    RtlGetCallersAddress. Use the intrinsic _ReturnAddress instead.
    就是本函数的返回地址,也就是调用本函数的下一个指令的地址。
    在调试器里面就是Retaddr。
    */
    PVOID CallersAddress = _ReturnAddress();

    //http://msdn.microsoft.com/zh-cn/library/windows/hardware/Dn613940(v=vs.85).aspx
    int stacklength = 32 * 1024;//取X86/X64/IA64的栈的最大值。

    UNICODE_STRING NtOpenKey = RTL_CONSTANT_STRING(L"NtOpenKey");
    PVOID p_NtOpenKey = MmGetSystemRoutineAddress(&NtOpenKey);//此函数没有导出。获取的值恒为0.

    ULONG_PTR  LowLimit = 0;//Pointer to a caller-supplied variable in which this routine returns the lower offset of the current thread's stack frame. 
    ULONG_PTR  HighLimit = 0;//Pointer to a caller-supplied variable in which this routine returns the higher offset of the current thread's stack frame.
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //下面开始代码。

    IoGetStackLimits(&LowLimit, &HighLimit);

    BackTrace = (PVOID  *)ExAllocatePool(NonPagedPool, stacklength); 
    if (BackTrace == NULL)
    {
        return ;
    }
    RtlZeroMemory(BackTrace, stacklength);
    /*
    BackTrace的地址一定在LowLimit和HighLimit之间,这是废话。
    */

    ncf = RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture, BackTrace, &BackTraceHash);     
    /*
    这行代码运行后的效果类似为:
    3: kd> dps esp
    efaf6948  efaf7000
    efaf694c  00000000
    efaf6950  0000003e
    efaf6954  ebbd2e00
    efaf6958  00009000
    efaf695c  82161000
    efaf6960  e1d10009
    efaf6964  efaf6970
    efaf6968  f8870132 test!post_open_key+0x12 [e:\code\driver\test\test.c @ 58]
    efaf696c  00000000
    efaf6970  efaf6980
    efaf6974  f887005d test!RegistryCallback+0x1d [e:\code\driver\test\test.c @ 74]
    efaf6978  efaf6b44
    efaf697c  0000000d
    efaf6980  efaf69b4
    efaf6984  8056bdd4 nt!CmpCallCallBacks+0x50
    efaf6988  00000000
    efaf698c  0000000d
    efaf6990  efaf6b44
    efaf6994  00000000
    efaf6998  e16d2518
    efaf699c  e1d1ab98
    efaf69a0  e1a02d90
    efaf69a4  e1a02d90
    efaf69a8  81ed9da0
    efaf69ac  8055b214 nt!CmpRegistryLock+0x34
    efaf69b0  00000000
    efaf69b4  efaf6b88
    efaf69b8  806312bd nt!CmpParseKey+0x7b9
    efaf69bc  0000000d
    efaf69c0  efaf6b44
    efaf69c4  e1bb5558
    3: kd> dps BackTrace
    efaf695c  82161000
    efaf6960  e1d10009
    efaf6964  efaf6970
    efaf6968  f8870132 test!post_open_key+0x12 [e:\code\driver\test\test.c @ 58]
    efaf696c  00000000
    efaf6970  efaf6980
    efaf6974  f887005d test!RegistryCallback+0x1d [e:\code\driver\test\test.c @ 74]
    efaf6978  efaf6b44
    efaf697c  0000000d
    efaf6980  efaf69b4
    efaf6984  8056bdd4 nt!CmpCallCallBacks+0x50
    efaf6988  00000000
    efaf698c  0000000d
    efaf6990  efaf6b44
    efaf6994  00000000
    efaf6998  e16d2518
    efaf699c  e1d1ab98
    efaf69a0  e1a02d90
    efaf69a4  e1a02d90
    efaf69a8  81ed9da0
    efaf69ac  8055b214 nt!CmpRegistryLock+0x34
    efaf69b0  00000000
    efaf69b4  efaf6b88
    efaf69b8  806312bd nt!CmpParseKey+0x7b9
    efaf69bc  0000000d
    efaf69c0  efaf6b44
    efaf69c4  e1bb5558
    efaf69c8  00000000
    efaf69cc  efaf6c40
    efaf69d0  e63561d1
    efaf69d4  81220010
    efaf69d8  e1b9fb18
    可见这个函数的栈顶少几个,但是不影响查找以前的函数和参数。
    多几个的原因可能是又调用函数了或者局部变量的初始化。

    BackTrace的这段内容就是函数的调用关系。
    */

    /*
    这里打印的信息类似于WINDBG的K命令,但是信息不多。
    */
    for (pBackTrace = BackTrace;i < ncf;pBackTrace++,i++ )
    {
        //if (*pBackTrace == p_NtOpenKey)//ZwOpenKey可以直接使用。
        //{
        //    break;
        //}
        KdPrint(("%d: %p \n", i, *pBackTrace));
    }

    /*
    要想获取某个函数的参数信息
    还得用IoGetStackLimits(&LowLimit, &HighLimit)的两个参数的值。
    在那个范围内搜索。
    */

    /*
    RtlWalkFrameChain和RtlCaptureStackBackTrace功能相似。
    RtlCaptureStackBackTrace封装了RtlWalkFrameChain(见WDK)。
    只不过:
    1.RtlWalkFrameChain导出(且头文件有函数申明)但是没有公开。
    2.RtlWalkFrameChain还可以获取包括(加上)用户态栈帧的数目。
    3.#pragma optimize( "y", off ) // disable FPO
    */
    //ncf = (USHORT)RtlWalkFrameChain (BackTrace, FramesToCapture, 0);  
    //ncf = (USHORT)RtlWalkFrameChain (BackTrace, FramesToCapture, 1); 

    ExFreePool(BackTrace);
}


NTSTATUS post_open_key(__in_opt PVOID  Argument2)
{
    unsigned int status = STATUS_SUCCESS;

    get_open_information_from_stack_in_32();

    return status;
}


EX_CALLBACK_FUNCTION RegistryCallback;
NTSTATUS RegistryCallback(__in PVOID  CallbackContext, __in_opt PVOID  Argument1, __in_opt PVOID  Argument2)
{
    switch((int)Argument1)
    {  
    case RegNtPostOpenKey:// == 13 仅仅XP收到此消息.
        post_open_key(Argument2);
        break;
    default:
        break;
    }

    return STATUS_SUCCESS;
}


DRIVER_UNLOAD Unload;
VOID Unload(__in PDRIVER_OBJECT DriverObject)
{   
    CmUnRegisterCallback(Cookie);
}


#pragma INITCODE
DRIVER_INITIALIZE DriverEntry;
NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT * DriverObject, __in PUNICODE_STRING RegistryPath)
{
    KdBreakPoint();

    DriverObject->DriverUnload = Unload; 

    return CmRegisterCallback(RegistryCallback, NULL, &Cookie);
} 

1 条评论: