使用DbgHelp獲取函式呼叫堆疊之inline assembly(內聯彙編)法

工程師WWW發表於2013-12-04

如果想自己獲取應用程式的Call Stack,就需要檢視Stack的內容。Stack Walker,在最近檢視SSCLI原始碼的時候發現這個東西是和Stack Frame緊密聯絡在一起的。

Walking the Stack

We could conceivably attempt to unwind the stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler optimizations and calling conventions, so it could become complicated to do it that way. Once again, Microsoft has provided us with a tool to help us out. This time it is a function that we can call iteratively to walk the stack, frame by frame. That function isStackWalk64. It is part of the Debug Help Library (dbghelp.dll). As long as we provide it with the information that it needs to establish a starting "frame of reference", so to speak, it can examine our stack from there and reliably unwind it for us. Each time StackWalk64 is called, it gives back a STACKFRAME64 structure that can be reused as input for the next call to StackWalk64. It can be repeatedly called this way until the end of the stack is reached.

       從上面的Walking the Stack可以看到,檢視App的Stack有兩種方法。使用內聯彙編或者是使用DbgHelp庫裡面的StackWalk64方法。

       找到DbgHelp裡面是StackWalk64:

 

BOOL

IMAGEAPI

StackWalk64(

DWORD

MachineType,

HANDLE

hProcess,

HANDLE

hThread,

LPSTACKFRAME64

StackFrame,

PVOID

ContextRecord,

PREAD_PROCESS_MEMORY_ROUTINE64

ReadMemoryRoutine,

PFUNCTION_TABLE_ACCESS_ROUTINE64

FunctionTableAccessRoutine,

PGET_MODULE_BASE_ROUTINE64

GetModuleBaseRoutine,

PTRANSLATE_ADDRESS_ROUTINE64

TranslateAddress

);

 

       可以參考MSDN裡面:http://msdn2.microsoft.com/en-us/library/ms680650(VS.85).aspx的關於這個方法的詳細使用。

       Kevin Lynx也給做了關於使用這個方法來獲取呼叫堆疊的例子:

http://www.cppblog.com/kevinlynx/archive/2008/03/28/45628.html

 

這裡,就給出一個採用內聯彙編來獲取App呼叫堆疊的例子,這個例子裡面,由於除錯生成的符號檔案沒有載入好,故而Mudule的資訊不能很好的顯示出來,不過這個例子很好的演示了使用內聯彙編來獲取Stack Frame,從而print出整個函式的呼叫堆疊來,同時也是一個很好的使用DbgHelp來獲取除錯資訊的例子:

#include "stdafx.h"

#include <windows.h>

#include <dbghelp.h>

 

#define INVALID_FP_RET_ADDR_VALUE 0x00000000

 

BOOL g_fSymInit;

HANDLE g_hProcess;

 

      

//address of the founction stack-call to walk.

BOOL DisplaySymbolDetails(DWORD dwAddress)

{

       DWORD64 displacement = 0;

 

       ULONG64 buffer[(sizeof(SYMBOL_INFO) +

    MAX_SYM_NAME*sizeof(TCHAR) +

    sizeof(ULONG64) - 1) /

    sizeof(ULONG64)];

       PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;

 

       pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);

       pSymbol->MaxNameLen = MAX_SYM_NAME;

 

 

       if (SymFromAddr(g_hProcess,dwAddress,&displacement,pSymbol))

       {

              // Try to get the Module details

              IMAGEHLP_MODULE64 moduleinfo;

              moduleinfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);

              if (SymGetModuleInfo64(g_hProcess,pSymbol->Address,&moduleinfo))

              {

                     printf("%s!",moduleinfo.ModuleName);

              }

              else

              {

                     printf("<ErrorModuleInfo_%d>!", GetLastError());

              }

 

              // now print the function name

              if (pSymbol->MaxNameLen > 0)

              {

                     printf("%s",pSymbol->Name);

              }

              else

              {

                     printf("<Unknown_Function>");

              }

       }

       else

       {

              printf(" <Unable to get symbol details_%d>", GetLastError());

       }

 

       return TRUE;

}

 

 

//採用內聯彙編獲取當前stack Frame地址和當前程式指令地址.

bool WalkTheStack()

{

       DWORD _ebp = INVALID_FP_RET_ADDR_VALUE;

       DWORD dwIPOfCurrentFunction = (DWORD)&WalkTheStack;

 

       // Get the current Frame pointer

       __asm

       {

              mov [_ebp], ebp

       }

      

       // We cannot walk the stack (yet!) without a frame pointer

       if (_ebp == INVALID_FP_RET_ADDR_VALUE)

              return false;

      

       printf("CurFP\t\t\tRetAddr\n");

 

       //current Frame Pointer

       DWORD *pCurFP = (DWORD *)_ebp;

       BOOL fFirstFP = TRUE;

       while (pCurFP != INVALID_FP_RET_ADDR_VALUE)

       {

              // pointer arithmetic works in terms of type pointed to. Thus,

              // "+1" below is equivalent of 4 bytes since we are doing DWORD

              // math.

              // Find Caller,next print.

              DWORD pRetAddrInCaller = (*((DWORD *)(pCurFP + 1)));

              printf("%p\t\t%p  ",pCurFP, (DWORD *)pRetAddrInCaller);

             

              if (g_fSymInit)

              {

                     if (fFirstFP)

                     {

                            fFirstFP = FALSE;

                     }

 

                     DisplaySymbolDetails(dwIPOfCurrentFunction);

 

                     // To get the name of the next function up the stack,

                     // we use the return address of the current frame

                     dwIPOfCurrentFunction = pRetAddrInCaller;

              }

 

              printf("\n");

              if (pRetAddrInCaller == INVALID_FP_RET_ADDR_VALUE)

              {

                     // StackWalk is over now...

                     break;

              }

 

              // move up the stack to our caller

              DWORD pCallerFP = *((DWORD *)pCurFP);

              pCurFP = (DWORD *)pCallerFP;

       }

      

       return true;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

       // Initialize the debugger services to retrieve detailed stack info

       g_fSymInit = FALSE;

       g_hProcess = GetCurrentProcess();

       if (!SymInitialize(g_hProcess, NULL,TRUE))

       {

              printf("Unable to initialize symbols!\n\n");    

       }

       g_fSymInit = TRUE;

 

       //SYMOPT_UNDNAME:All symbols are presented in undecorated form.

       //SYMOPT_INCLUDE_32BIT_MODULES:

//When debugging on 64-bit Windows, include any 32-bit modules.

       //SYMOPT_ALLOW_ABSOLUTE_SYMBOLS:

//Enables the use of symbols that are stored with absolute addresses. instead of RAVS forms.

       SymSetOptions(SYMOPT_UNDNAME|SYMOPT_INCLUDE_32BIT_MODULES|SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);

 

       if (WalkTheStack() == false)

              printf("Stackwalk failed!\n");

 

       return 0;

}

 

      兩個程式種需要用到的比較重要的結構體,一個是關於某個方法Symbol資訊的
typedef struct _SYMBOL_INFO,另外一個是關於模組資訊的,
typedef struct _IMAGEHLP_MODULE64.
     
下面是執行這個程式碼列印出的呼叫程式碼示意圖:

r_aaaa.JPG

相關文章