为了跟踪错误,经常需要获取程序中各线程的函数调用栈信息,通过函数 StackWalk 来获取。

BOOL WINAPI StackWalk64(
  __in      DWORD MachineType,
  __in      HANDLE hProcess,
  __in      HANDLE hThread,
  __inout   LPSTACKFRAME64 StackFrame,
  __inout   PVOID ContextRecord,
  __in_opt  PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
  __in_opt  PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
  __in_opt  PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
  __in_opt  PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);

以下是示例代码:

void get_callstack()
{
    HANDLE hProcess;    // 目标进程(需设置)
    HANDLE hThread;        // 目标线程(需设置)
    CONTEXT* context;    // 目标线程上下文(需设置)
    DWORD machineType;    // 机器Cpu类型

    STACKFRAME sf;        // 信息
    memset( &sf, 0, sizeof( STACKFRAME ) );

#ifdef _M_IX86
    machineType = IMAGE_FILE_MACHINE_I386;
    sf.AddrPC.Offset = context->Eip;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrStack.Offset = context->Esp;
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = context->Ebp;
    sf.AddrFrame.Mode = AddrModeFlat;
#elif _M_X64
    machineType = IMAGE_FILE_MACHINE_AMD64;
    sf.AddrPC.Offset = context->Rip;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrStack.Offset = context->Rsp;
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = context->Rsp;
    sf.AddrFrame.Mode = AddrModeFlat;
#elif _M_IA64
    machineType = IMAGE_FILE_MACHINE_IA64;
    sf.AddrPC.Offset = context->StIIP;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrStack.Offset = context->IntSp;
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = context->IntSp;
    sf.AddrFrame.Mode = AddrModeFlat;
    sf.AddrBStore.Offset = context->RsBSP;
    sf.AddrBStore.Mode = AddrModeFlat;
#else
#error "platform not supported!"
#endif
    
    unsigned deep = 0;
    for( ; ; )
    {
        if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
            break;

        if( sf.AddrFrame.Offset == 0 )
            break;

        // method name
        BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
        PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;
        memset(pSymbol, 0, sizeof( SYMBOL_INFO ) + 1024);

        pSymbol->SizeOfStruct = sizeof( symbolBuffer );
        pSymbol->MaxNameLen = 1024;

        if( SymGetSymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
        {
            printf( "[Function : %s]\n", pSymbol->Name );        // 函数名
        }

        // file and line
        IMAGEHLP_LINE lineInfo;
        memset(&lineInfo, 0 , sizeof(IMAGEHLP_LINE));
        lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE);

        DWORD dwLineDisplacement;
        if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
        {
            printf( "[Source File : %s]\n", lineInfo.FileName );    // 文件路径
            printf( "[Source Line : %u]\n", lineInfo.LineNumber );    // 代码所在行
        }
        
        // module
        IMAGEHLP_MODULE moduleInfo;
        memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE));
        moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
        if (SymGetModuleInfo( hProcess, sf.AddrPC.Offset, &moduleInfo))
        {
            printf( "[Module : %s]\n", moduleInfo.ModuleName );    // 模块
        }

        // 防止死循环
        if (++deep > 100)
            break;
    }
}

 

注意事项:使用StackWalk获取线程函数调用栈,需要获取线程上下文,两种情况

  (1)线程抛出异常:这种情况下,异常结构体信息中就包含上下文信息,且线程处于已挂起状态,可通过 GetThreadContext 获取

  (2)线程中运行中:此时使用 GetThreadContext 获取上下文必须先将线程挂起,使用 SuspendThread ,当结束后再使用 ResumeThread 恢复线程。这是一个比较危险的操作,若线程挂起后,代码异常导致未调用 ResumeThread ,将导致该线程一直处于挂起状态。可以使用临时安全类的析构来解决,如下:

class SafeSuspend
{
public:
    SafeSuspend(HANDLE hThread) : m_hThread(hThread)
    {
        if (m_hThread != INVALID_HANDLE_VALUE)
            ::SuspendThread(m_hThread);
    }
    ~SafeSuspend()
    {
        if (m_hThread != INVALID_HANDLE_VALUE)
            ::ResumeThread(m_hThread);
    }

private:
    HANDLE m_hThread;
};

 

 

 

posted on 2013-02-18 12:06  布丁嫩  阅读(886)  评论(0编辑  收藏  举报