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

3
我正在使用Visual Studio的/Gh和/GH编译器选项来分析一些代码。所使用的两种方法是_penter和_pexit,用于在被分析的代码中进入或退出函数时调用。由于我需要对特定的函数进行分析/调试,我使用一个已经定义好的数组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
    }
}

1
C语言不支持“方法”,只支持“函数”。但是extern "C"并不是合法的C语言,而是另一种语言C++。请使用正确的标签。 - too honest for this site
4个回答

2

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

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


0

由于编译器确保在每个函数的开头和结尾调用_penter_pexit,因此您可以确定,在调用_pexit时,由_penter创建的堆栈顶部的函数指针始终指向当前函数。无需搜索它。

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


这种方法的问题在于,并非每个函数都被跟踪,而且由于我只在_penter中跟踪特定函数,在_pexit中我需要知道它是否确实是我在_penter中跟踪的函数。不幸的是,我无法为每个线程创建一个私有堆栈,该库应该与可以是单线程或多线程的应用程序链接。而且我认为我无法访问库链接到的进程的线程。 - Taha Rehman Siddiqui
@TahaRehmanSiddiqui 这并不妨碍使用(第二个)堆栈来跟踪堆栈(双关语)。此外,我不明白为什么你不能使用TLS。 - Phillip

0
原来我看问题的角度是错误的。我通过使用 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");

请注意,文档明确指出 SymFromAddr 不是线程安全的 - Phillip
好的,我刚刚为一段代码(模拟器)测试了一下。它可以使用多线程代码(可能是因为我在使用链接到多线程进程的库)。然而,它会将执行速度放慢到无法忍受的程度。所以我还在寻找办法。@Phillip,不知道为什么,如果我使用栈,在应该有一些条目的时候它就变为空了。您确认一下,使用栈的时候,我是在_penter中进行push操作,_pexit中进行pop操作对吧? - Taha Rehman Siddiqui
没错。只要你无条件地这样做,不手动调用这两个函数,并确保没有线程问题,就不会发生这种情况。 - Phillip
1
你可以缓存SymFromAddr的结果,并且只有在获取新地址时才查找符号。这样应该可以避免大部分性能损失。此外,在调用SymFromAddr时应该使用锁,因为它不是线程安全的(仅仅因为它似乎可以与多个线程一起工作并不能证明这样做是安全的 - 你需要两个线程同时进入函数才可能看到问题)。 - 1201ProgramAlarm

0

配置属性 > C/C++ > 命令行

附加选项 框中添加编译器选项

像这样 示例设置

添加标志 /Gh 以进行 _penter 钩子
添加标志 /GH 以进行 _pexit 钩子

我用于跟踪/记录的代码

#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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接