繁体   English   中英

跟踪程序中的所有函数

[英]Tracing all functions in my program

正如标题所说,我想跟踪我的应用程序中的所有函数调用(从内部)。

我尝试使用“_penter”,但是当我尝试阻止递归时,出现递归限制达到错误或访问冲突。

有没有办法实现这一目标?

更新

我试过的:

extern "C"
{
    void __declspec(naked) _cdecl _penter()
    {
        _asm {
            push    eax
            push    ecx
            push    edx
            mov     ecx, [esp + 0Ch]
            push    ecx
            mov     ecx, offset Context::Instance
            call    Context::addFrame
            pop     edx
            pop     ecx
            pop     eax
            ret
        }
}

class Context
{
 public:
    __forceinline void addFrame(const void* addr) throw() {}

    static thread_local Context Instance;
};

遗憾的是,由于递归,这仍然会导致堆栈溢出

您的方法是正确的, /Gh 和 /GH 编译器开关 + _penter 和 _pexit 函数是要走的路。

我认为您在实现这些功能时存在错误。 这是非常低级的东西,对于 32 位构建,您必须使用__declspec(naked) ,对于 64 位构建,您必须使用汇编程序。 两者都很难正确实施。

看看这个存储库的一个例子,如何正确地做到这一点: https : //github.com/tyoma/micro-profiler具体来说,这个源文件: https : //github.com/tyoma/micro-profiler/blob /master/micro-profiler/collector/hooks.asm如您所见,他们决定在两个平台上都使用汇编程序,并从中调用一些 C++ 函数来记录调用信息。 还要注意在 C++ 收集器实现中他们如何使用__forceinline来避免递归。

但我得到一个递归限制达到错误

这可以是如果在Context::addFrame实现编译器中也插入 call _penter ,它递归调用Context::addFrame

但是__forceinline你怎么能问? 没什么。 c/c++编译器将函数体的副本插入到从该编译器生成的代码调用函数的每个位置。 c/c++编译器不能将函数体的副本插入到代码中,而他自己不编译。 因此,当我们从汇编代码中调用标记为__forceinline - 函数将以通常的方式调用,但不会就地展开。 所以你的__forceinline根本没有效果和意义

您需要在没有/Gh选项的情况下单独的C++文件(假设是context.cpp )中实现 Context::addFrame (以及它调用的所有函数)。

您可以为项目中的所有文件设置/Gh ,除了context.cpp

如果项目中存在太多cpp文件 - 您可以为项目设置/Gh ,但是如何为单个文件context.cpp删除它? 存在一种原始方式 - 您可以复制此文件的<cmdline>为其设置自定义构建工具命令行- CL.exe <cmdline> $(InputFileName) (不要忘记删除/Gh )和输出- $(IntDir)\\$(InputName).obj 完美的作品原创。

所以在context.cpp你可以有下一个代码:

class Context
{
public:
    void __fastcall addFrame(const void* addr);

    int _n;

    static thread_local Context Instance;
};

thread_local Context Context::Instance;

void __fastcall Context::addFrame(const void* addr)
{
#pragma message(__FUNCDNAME__)

    DbgPrint("%p>%u\n", addr, _n++);
}

如果Context::addFrame调用另一个内部函数(显式或隐式) - 也将其放在此文件中,该文件在没有/Gh情况下进行编译

_penter更好地在单独的 asm 文件中实现,但不是内联 asm(无论如何 x64 都不支持)

所以对于 x86 你可以创建code32.asm ( ml /c /Cp $(InputFileName) -> $(InputName).obj )

.686p

.MODEL flat

extern ?addFrame@Context@@QAIXPBX@Z:proc
extern ?Instance@Context@@2V12@A:byte

_TEXT segment 'CODE'

__penter proc
    push edx
    push ecx
    mov edx,[esp+8]
    lea ecx,?Instance@Context@@2V12@A
    call ?addFrame@Context@@QAIXPBX@Z
    pop ecx
    pop edx
    ret
__penter endp

_TEXT ends
end

注意 - 您只需要保存rcxrdx (如果您使用__fastcall ,除了 context.cpp 函数)

对于 x64 - 创建code64.asm ( ml64 /c /Cp $(InputFileName) -> $(InputName).obj )

extern ?addFrame@Context@@QEAAXPEBX@Z:proc
extern ?Instance@Context@@2V12@A:byte

_TEXT segment 'CODE'

_penter proc
    mov [rsp+8],rcx
    mov [rsp+16],rdx
    mov [rsp+24],r8
    mov [rsp+32],r9
    mov rdx,[rsp]
    sub rsp,28h
    lea rcx,?Instance@Context@@2V12@A
    call ?addFrame@Context@@QEAAXPEBX@Z
    add rsp,28h
    mov r9,[rsp+32]
    mov r8,[rsp+24]
    mov rdx,[rsp+16]
    mov rcx,[rsp+8]
    ret
_penter endp

_TEXT ENDS
end

这是我使用的

Configuration Properties > C/C++ > Command Line

将编译器选项添加到Additional Options

像这样示例设置

为 _penter 钩子添加标志 /Gh
为 _pexit 钩子添加标志 /GH

我用于跟踪/记录的代码

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM