就像标题所说,我想跟踪应用程序中的所有函数调用(从内部)。
我尝试使用“_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/collector/src/hooks.asm 作为你看,他们决定在两个平台上使用汇编程序,并从中调用一些 C++ 函数来记录调用信息。另请注意,在 C++ 收集器实现中,他们如何使用
__forceinline
来避免递归。
但我收到递归限制达到错误
这可以是如果在
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
注意 - 您只需要保存 rcx 和 rdx (如果您使用
__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;
}
}