C++ - 从结束地址获取函数的起始地址/获取函数的大小

C++ - Get Starting Address of a function from Ending Address / Get size of a function

我正在使用 visual studio 的 /Gh 和 /GH 编译器选项来分析一堆代码。使用的两种方法是 _penter 和 _pexit,当在被分析的代码中进入或退出函数时调用它们。因为我需要特定的函数 profiled/debugged,所以我使用了一个已经定义的数组 FuncTable,其中包含我需要使用其名称作为字符串进行检测的函数的地址。所以当一个函数被输入时,pStack[0],它基本上包含寄存器内容,包含正在执行的代码的当前行的地址。类似地,当函数退出时,pStack[0] 包含最后一行代码的地址。

问题:当输入一个函数(调用_penter)时,我在pStack[0]中得到函数第一行的地址,因此我可以得到通过减去常量 (-5) 来获取函数的地址并将其保存到我的列表中,以便稍后在 _pexit 函数中检索。但是由于在 _pexit 中我得到函数最后一行的地址,我需要找到函数的大小以便从 pStack[0] 中的地址减去该大小以到达函数的起始地址然后进行比较该地址保存在我的列表中。下面粘贴的是代码。

void _stdcall EnterFunc0(unsigned * pStack)
{
    void      * pCaller;
    pCaller = (void *)(pStack[0] - 5); // pStack[0] is first line, -5 for function address
    Signature * funct = FuncTable;

    while (funct->function)
    {
        const BYTE * func = (const BYTE *)funct->function;
        if ((func == (const BYTE *)pCaller) || ((*func == 0xE9) && ((func + *(DWORD *)(func + 1) + 5) == (const BYTE *)pCaller)))
        {
            Stack_Push(funct->name, funct->returnType, true, pCaller);          
        }
        funct++;
    }

}

extern "C" __declspec(naked) void __cdecl _penter()
{
    _asm
    {
        pushad              // save all general purpose registers
        mov    eax, esp     // current stack pointer
        add    eax, 32      // stack pointer before pushad
        push   eax          // push pointer to return address as parameter to EnterFunc0

        call   EnterFunc0

        popad               // restore general purpose registers
        ret                 // start executing original function
    }
}
void _stdcall ExitFunc0(unsigned * pStack)
{
    if (startRecording) 
    {
        StackEntry * start = top;
        while (start != NULL)
        {
            //**HERE I NEED TO COMPARE THE ADDRESS OF THE FUNCTION WITH THE ONE ALREADY IN MY STACK**
                            if ((void *)(pStack[0] - sizeOfTheFunction) == start->Address)
            {
                OutputDebugString("Function Found\n");
            }
            start = start->next;
        }
    }

}
extern "C" __declspec(naked) void __cdecl _pexit()
{
    _asm
    {
        pushad              // save all general purpose registers
        mov    eax, esp     // current stack pointer
        add    eax, 32      // stack pointer before pushad
        push   eax          // push pointer to return address as parameter to EnterFunc0

        call   ExitFunc0

        popad               // restore general purpose registers
        ret                 // start executing original function
    }
}

由于编译器确保在每个函数的开始和结束时调用 _penter_pexit,因此您可以确定在调用 _pexit 时,函数指针在由 _penter 创建的堆栈顶部始终指向当前函数。不用再找了。

(这应该是正确的,除非你手动调用其中一个函数,你不应该这样做,或者有一个多线程程序。在后一种情况下,你应该为每个线程创建一个私有堆栈。当然,您还必须向 _pexit 添加 Stack_Pop 调用,但我假设您无论如何都计划这样做。)

原来我看问题的方式不对。我通过使用 SymFromAddress 方法从 dbghelp.lib 获取符号信息解决了这个问题。一旦我得到了方法的名称,我就能够将它与我存储在 _penter 中的信息进行比较。

SYMBOL_INFO  * mysymbol;
HANDLE         process;
char           temp[MAX_TEMP_LENGTH] = "                                                                       ";

process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);

mysymbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
mysymbol->MaxNameLen = 255;
mysymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
SymFromAddr(process, (DWORD64)((void *)pStack[0]), 0, mysymbol);
OutputDebugString(mysymbol->Name);
OutputDebugString("\n");

您已经知道 _pexit() 函数中的地址,它是在 _penter() 函数中传递给您的。您所要做的就是支持嵌套函数调用。 std::stack<> 对此有好处。使用 push() 将地址保存在 _penter 中,在 _pexit 函数中使用 top() 检索它并调用 pop()。

根本不需要知道函数体的大小。

Configuration Properties > C/C++ > Command Line

将编译器选项添加到 Additional Options 框中

像这样

Add flag /Gh for _penter hook
Add flag /GH for _pexit hook

我用于跟踪/记录的代码

#include <intrin.h>

extern "C"  void __declspec(naked) __cdecl _penter(void) {
    __asm {
        push ebp;               // standard prolog
        mov ebp, esp;
        sub esp, __LOCAL_SIZE
        pushad;                 // save registers
    }
    // _ReturnAddress always returns the address directly after the call, but that is not the start of the function!
    PBYTE addr;
    addr = (PBYTE)_ReturnAddress() - 5;

    SYMBOL_INFO* mysymbol;
    HANDLE       process;
    process = GetCurrentProcess();
    SymInitialize(process, NULL, TRUE);
    mysymbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
    mysymbol->MaxNameLen = 255;
    mysymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    SymFromAddr(process, (DWORD64)((void*)addr), 0, mysymbol);
    myprintf("Entered Function: %s [0x%X]\n", mysymbol->Name, addr);

    _asm {
        popad;              // restore regs
        mov esp, ebp;       // standard epilog
        pop ebp;
        ret;
    }
}

extern "C"  void __declspec(naked) __cdecl _pexit(void) {
    __asm {
        push ebp;               // standard prolog
        mov ebp, esp;
        sub esp, __LOCAL_SIZE
        pushad;                 // save registers
    }
    // _ReturnAddress always returns the address directly after the call, but that is not the start of the function!
    PBYTE addr;
    addr = (PBYTE)_ReturnAddress() - 5;

    SYMBOL_INFO* mysymbol;
    HANDLE       process;
    process = GetCurrentProcess();
    SymInitialize(process, NULL, TRUE);
    mysymbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
    mysymbol->MaxNameLen = 255;
    mysymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    SymFromAddr(process, (DWORD64)((void*)addr), 0, mysymbol);
    myprintf("Exit Function: %s [0x%X]\n", mysymbol->Name, addr);

    _asm {
        popad;              // restore regs
        mov esp, ebp;       // standard epilog
        pop ebp;
        ret;
    }
}