确定调用者模块路径的C++实现

在软件开发中,有时需要知道是哪个模块调用了函数。这可能是为了验证调用者是否经过认证,或者根据调用上下文返回不同的结果。本文基于Chavdar Dimitrov的文章,对其进行了扩展,使其能够处理.NET调用者的情况,并解决了一些在Windows安全策略下可能遇到的问题。

Dimitrov的文章主要针对原生代码,但并未涉及.NET调用者的情况。旧的DLL可能会在发布版本中崩溃,这可能是由于Windows安全策略的加强。因此,对其进行了改进,以满足新的需求,尽管功能有所减少。

代码分析

函数是用C++编写的,以便能够轻松地从托管和非托管代码中调用。提供了DLL和EXE文件,以应对可能出现的各种情况。此外,必须安装.NET 2.0运行时,因为stackdumper.dll是一个混合DLL,其中某些文件是用/clr选项编译的。外部代码将调用GetCallingModulePath,这是stackdumper.dll中的一个入口点。出于某种原因,这个函数必须有一个参数。

const char* _stdcall GetCallingModulePath(int arg) { long reg_ebd; __asm { mov eax, ebp mov reg_ebd, eax } ADDR callerAddr; unsigned i = 0; HANDLE h = GetCurrentProcess(); module.empty(); callerAddr = GetCallerAddr(reg_ebd); if (callerAddr == 0) goto last; if (getFuncInfo(callerAddr,module) > 0) { BOOL bnet = IsDotNetRuntime((char*)module.c_str()); if (bnet) { BOOL bres = GetDotNetCallerFileName(module); if (bres == TRUE) goto last; } } long temp = 0; SIZE_T cnt; long* p = (long*)reg_ebd; BOOL bres = ReadProcessMemory(h,(LPCVOID)p,(LPVOID)&temp,sizeof(long),&cnt); reg_ebd = temp; i++; last: // ... }

如果想要理解原生代码中的栈跟踪是如何工作的,应该从上面提到的Dimitrov的文章开始。

重点

GetDotNetCallerFileName托管函数将跳过栈中的所有.NET运行时程序集,返回实际调用函数的程序集。

int GetDotNetCallerFileName(string& module) { int res = FALSE; try { Assembly^ callerAssembly = Assembly::GetCallingAssembly(); if (callerAssembly == nullptr) return FALSE; String^ sysdir = System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory(); // 跳过所有.NET框架程序集和来自同一程序集的调用 String^ strCallerPath = callerAssembly->Location; String^ directoryName = nullptr; if (strCallerPath != nullptr) directoryName = Path::GetDirectoryName(strCallerPath) + "\\"; while (directoryName != nullptr && (String::Compare(sysdir, directoryName, true) == 0) || callerAssembly == Assembly::GetExecutingAssembly()) { strCallerPath = nullptr; callerAssembly = callerAssembly->GetCallingAssembly(); directoryName = nullptr; if (strCallerPath != nullptr) directoryName = Path::GetDirectoryName(strCallerPath) + "\\"; strCallerPath = callerAssembly->Location; } // ... } }

调用代码

对于原生调用,无论是直接调用还是间接调用,一切都很直接:

const char* szc = GetCallingModulePath(1); printf("expected result: test.exe\nfinal result = %s\n\n", szc); szc = NativeDllCall(1); printf("expected result: Nativedll.dll\nfinal result = %s\n", szc); char** pszc = NULL, **original = NULL; int cnt = GetModuleStackTraceFromNative(pszc); original = pszc; printf("\nstack trace---------\n"); while (cnt-- > 0) { printf("Trace: = %s\n", *pszc); CoTaskMemFree(*pszc); pszc++; } CoTaskMemFree(original);

不需要在调用GetCallingModulePath后调用CoTaskMemFree,因为返回的指针是stackdumper.dll中的一个全局变量。如果从托管代码(如C#)调用,必须将方法作为extern import

[DllImport("stackdumper.dll", CharSet = CharSet.Ansi)] static extern string GetCallingModulePath(int arg);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485