跟踪我的程序中的所有函数

问题描述 投票:0回答:3

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

我尝试使用“_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;
};

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

c++ windows visual-c++
3个回答
4
投票

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

我认为你在实现这些功能时存在错误。这是非常低级的东西,对于 32 位构建,你必须使用

__declspec(naked)
,对于 64 位构建,你必须使用汇编程序。正确实施两者都非常棘手。

查看此存储库,了解如何正确执行此操作的示例: https://github.com/tyoma/micro-profiler 具体来说,对于此源文件:https://github.com/tyoma/micro-profiler/blob/master/collector/src/hooks.asm 作为你看,他们决定在两个平台上使用汇编程序,并从中调用一些 C++ 函数来记录调用信息。另请注意,在 C++ 收集器实现中,他们如何使用

__forceinline
来避免递归。


2
投票

但我收到递归限制达到错误

这可以是如果在

Context::addFrame
实现编译器中还插入递归调用
_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

1
投票

这是我用的

Configuration Properties > C/C++ > Command Line

将编译器选项添加到

Additional Options

像这样example settings

为 _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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.