我正在阅读this问题,因为我试图在C ++程序中找到一个函数的大小。有人暗示可能有一种特定于平台的方法。我的目标平台是windows
我目前掌握的方法如下:
1.获取指向功能的指针
2.递增指针(&计数器),直到达到ret
的机器代码值
3.计数器将是函数的大小?
Edit1:为了澄清'size'的含义,我的意思是构成该功能的字节数(机器码)。
编辑2:有一些评论询问为什么或我打算如何处理这个问题。诚实的答案是我没有意图,我无法真正看到了解函数长度预编译时间的好处。 (虽然我确定有一些)
这对我来说似乎是一种有效的方法,这会起作用吗?
答案 0 :(得分:13)
可以获得函数的所有块,但是询问函数的“大小”是一个不自然的问题。优化的代码将按执行顺序重新排列代码块,并将很少使用的块(异常路径)移动到模块的外部部分。有关更多详细信息,请参阅Profile-Guided Optimizations,例如Visual C ++如何在链接时代码生成中实现此目的。因此,一个函数可以从地址0x00001000开始,在0x00001100处跳转到0x20001000的跳转和一个ret,并且有一些异常处理代码0x20001000。在0x00001110,另一个函数启动。你的功能的“大小”是多少?它确实从0x00001000到+ 0x20001000,但它'拥有'在该范围内只有几个块。所以你的问题应该没有问题。
在此上下文中还有其他有效问题,例如函数的指令总数(可以从程序符号数据库和图像中确定),更重要的是,频繁执行的指令数是多少函数内部的代码路径。所有这些都是通常在绩效衡量的背景下提出的问题,并且有工具可以检测代码并提供非常详细的答案。
在内存中追逐指针并搜索ret
会让你无所畏惧。现代代码比这更复杂。
答案 1 :(得分:11)
不,这不起作用:
ret
指令。ret
,您也不能只查看单个字节 - 因为相应的值可能只是一个值,而不是一条指令。如果你将编码风格限制在你的功能中只有一个单一的返回点,那么第一个问题可能会解决,但另一个基本上需要一个反汇编程序,这样你就可以区分各个指令了。 p>
答案 2 :(得分:10)
static void funcIwantToCount()
{
// do stuff
}
static void funcToDelimitMyOtherFunc()
{
__asm _emit 0xCC
__asm _emit 0xCC
__asm _emit 0xCC
__asm _emit 0xCC
}
int getlength( void *funcaddress )
{
int length = 0;
for(length = 0; *((UINT32 *)(&((unsigned char *)funcaddress)[length])) != 0xCCCCCCCC; ++length);
return length;
}
静态函数似乎效果更好。全局优化可以杀死它。
P.S。我讨厌别人,问你为什么要这样做,这是不可能的等等。请不要再问这些问题了。让你听起来很愚蠢。程序员经常被要求做非标准的事情,因为新产品几乎总是突破了可用的极限。如果他们不这样做,那么您的产品可能是对已经完成的工作的重复。镗!!!
答案 3 :(得分:7)
这不起作用......如果有跳跃,虚拟ret
,然后跳跃的目标怎么办?你的代码会被愚弄。
一般情况下,以100%的准确度执行此操作不可能,因为您必须预测所有代码路径,这就像解决halting problem一样。如果你实现自己的反汇编程序,你可以获得“非常好”的准确性,但没有任何解决方案可以像你想象的那样容易。
一个“诀窍”就是找出之后哪个函数的代码是你正在寻找的函数,这会给相当不错的结果假设某些(危险的) )假设。但是你必须知道在你的函数之后出现了什么函数,在优化之后很难弄明白。
编辑1:
如果函数甚至没有以ret
指令结束,该怎么办?它很可能只是jmp
回到它的调用者(尽管不太可能)。
编辑2:
不要忘记x86至少有可变长度指令......
对于那些说流量分析与解决暂停问题不同的人:
考虑当您拥有以下代码时会发生什么:
foo:
....
jmp foo
你 每次都必须按照跳转来找出函数的结尾,并且不能在第一次之后忽略它,因为你不知道你是否正在处理自修改代码。 (例如,您可以在C ++代码中使用内联汇编来修改自身。)它可以很好地扩展到其他内存位置,因此您的分析器将(或应该)以无限循环结束,除非您容忍漏报。
这不是停滞问题吗?
答案 4 :(得分:2)
真正的解决方案是深入了解编译器的文档。我们使用的ARM编译器可以生成一个汇编转储(code.dis),从中减去给定的错位函数标签和下一个错位函数标签之间的偏移量是非常简单的。
但是,我不确定使用Windows目标需要哪些工具。看起来this question答案中列出的工具可能就是您所需要的。
另请注意,我(在嵌入式空间中工作)假设您正在讨论编译后分析。仍然可以以编程方式检查这些中间文件,作为构建的一部分,提供:
请注意,我不确定您想知道这些信息的原因。我过去需要它,以确保我可以在内存中的特定位置放入特定的代码块。我不得不承认,我很好奇这对于更通用的桌面操作系统目标会有什么用途。
答案 5 :(得分:2)
此可以在非常有限的情况下工作。我在我编写的代码注入实用程序的一部分中使用它。我不记得我在哪里找到了这些信息,但我有以下内容(VS2005中的C ++):
#pragma runtime_checks("", off)
static DWORD WINAPI InjectionProc(LPVOID lpvParameter)
{
// do something
return 0;
}
static DWORD WINAPI InjectionProcEnd()
{
return 0;
}
#pragma runtime_checks("", on)
然后在我的其他一些功能中:
size_t cbInjectionProc = (size_t)InjectionProcEnd - (size_t)InjectionProc;
你必须关闭一些优化并将函数声明为静态以使其工作;我不记得具体细节。我不知道这是否是一个确切的字节数,但它足够接近。大小只是直接功能的大小;它不包括该函数可能调用的任何其他函数。除了这样的极端边缘情况之外,“函数的大小”毫无意义且毫无用处。
答案 6 :(得分:1)
你的意思是“功能的大小”是什么意思?
如果你的意思是一个函数指针,那么32位系统总是只有4个字节。
如果您的意思是代码的大小,那么您应该反汇编生成的代码并找到最近的ret
调用的入口点。一种方法是在函数的开头和结尾读取指令指针寄存器。
如果要计算出函数平均情况下调用的指令数,可以使用分析器并将已退休指令的数量除以调用次数。
答案 7 :(得分:1)
在C ++中,没有函数大小的概念。除了提到的所有其他内容之外,预处理器宏也会产生不确定的大小。如果要计算指令字数,则不能在C ++中这样做,因为它在编译之前不存在。
答案 8 :(得分:1)
我认为它可以在用msvc创建的windows程序上工作,至于分支'ret'似乎总是在最后(即使有早期返回的分支,它会做一个jne到最后)。 但是,您需要某种反汇编程序库来计算当前的操作码长度,因为它们是x86的可变长度。如果你不这样做,你会遇到误报。
如果有些情况没有发现,我不会感到惊讶。
答案 9 :(得分:1)
标准C ++中没有设施来获取功能的大小或长度 请在此处查看我的回答:Is it possible to load a function into some allocated memory and run it from there?
通常,在将可执行代码从只读源(或慢速存储器设备,如串行闪存)复制到RAM时,在嵌入式系统中使用了解函数的大小。桌面和其他操作系统使用其他技术将函数加载到内存中,例如动态或共享库。
答案 10 :(得分:1)
我发帖说这两件事:
1)这里给出的大多数答案非常糟糕且容易破解。如果您使用C函数指针(使用函数名称),在可执行文件的debug
版本中,并且可能在其他情况下,它可能指向JMP
shim 那将没有功能体本身。这是一个例子。如果我为以下定义的函数执行以下操作:
FARPROC pfn = (FARPROC)some_function_with_possibility_to_get_its_size_at_runtime;
我得到的pfn
(例如:0x7FF724241893
)将指向此,这只是JMP
指令:
此外,编译器可以嵌套其中几个填充程序,或者分支您的功能代码,以便它具有多个 epilogs 或ret
指令。哎呀,它甚至可能不会使用ret
指令。然后,无法保证函数本身将按照您在源代码中定义它们的顺序进行编译和链接。
你可以用汇编语言完成所有这些工作,但不能用C或C ++。
2)以上是坏消息。好消息是,对原始问题的回答是,是的,有一种方法(或 hack )来获得确切的函数大小,但它附带以下内容限制:
它仅适用于Windows上的64位可执行文件。
显然是微软特有的,不可移植。
您必须在运行时执行此操作。
概念很简单 - 在x64 Windows二进制文件中使用SEH is implemented的方式。编译器将每个函数的详细信息添加到PE32 +标头(可选标头的IMAGE_DIRECTORY_ENTRY_EXCEPTION
目录中),您可以使用它来获取确切的函数大小。 (如果您想知道,此信息用于在__try/__except/__finally
块中捕获,处理和展开异常。)
这是一个简单的例子:
//You will have to call this when your app initializes and then
//cache the size somewhere in the global variable because it will not
//change after the executable image is built.
size_t fn_size; //Will receive function size in bytes, or 0 if error
some_function_with_possibility_to_get_its_size_at_runtime(&fn_size);
然后:
#include <Windows.h>
//The function itself has to be defined for two types of a call:
// 1) when you call it just to get its size, and
// 2) for its normal operation
bool some_function_with_possibility_to_get_its_size_at_runtime(size_t* p_getSizeOnly = NULL)
{
//This input parameter will define what we want to do:
if(!p_getSizeOnly)
{
//Do this function's normal work
//...
return true;
}
else
{
//Get this function size
//INFO: Works only in 64-bit builds on Windows!
size_t nFnSz = 0;
//One of the reasons why we have to do this at run-time is
//so that we can get the address of a byte inside
//the function body... we'll get it as this thread context:
CONTEXT context = {0};
RtlCaptureContext(&context);
DWORD64 ImgBase = 0;
RUNTIME_FUNCTION* pRTFn = RtlLookupFunctionEntry(context.Rip, &ImgBase, NULL);
if(pRTFn)
{
nFnSz = pRTFn->EndAddress - pRTFn->BeginAddress;
}
*p_getSizeOnly = nFnSz;
return false;
}
}
答案 11 :(得分:0)
只需在您获得功能的地址设置PAGE_EXECUTE_READWRITE即可。然后读取每个字节。当你得到字节“0xCC”时,意味着函数的结尾是actual_reading_address - 1。
答案 12 :(得分:0)
使用GCC,根本不是那么难。
void do_something(void) {
printf("%s!", "Hello your name is Cemetech");
do_something_END:
}
...
printf("size of function do_something: %i", (int)(&&do_something_END - (int)do_something));
答案 13 :(得分:0)
下面的代码获得准确的功能块大小,它适用于我的测试 runtime_checks在调试模式下禁用_RTC_CheckEsp
#pragma runtime_checks("", off)
DWORD __stdcall loadDll(char* pDllFullPath)
{
OutputDebugStringA(pDllFullPath);
//OutputDebugStringA("loadDll...................\r\n");
return 0;
//return test(pDllFullPath);
}
#pragma runtime_checks("", restore)
DWORD __stdcall getFuncSize_loadDll()
{
DWORD maxSize=(PBYTE)getFuncSize_loadDll-(PBYTE)loadDll;
PBYTE pTail=(PBYTE)getFuncSize_loadDll-1;
while(*pTail != 0xC2 && *pTail != 0xC3) --pTail;
if (*pTail==0xC2)
{ //0xC3 : ret
//0xC2 04 00 : ret 4
pTail +=3;
}
return pTail-(PBYTE)loadDll;
};
答案 14 :(得分:0)
不可移植但基于API且可以正常工作的方法是使用程序数据库读取器-例如Windows上的dbghelp.dll或Linux上的readelf。只有在调试信息与程序一起启用/存在调试信息的情况下,才可以使用它们。这是一个在Windows上如何工作的示例:
SYMBOL_INFO symbol = { };
symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
// Implies, that the module is loaded into _dbg_session_handle, see ::SymInitialize & ::SymLoadModule64
::SymFromAddr(_dbg_session_handle, address, 0, &symbol);
您将在 symbol.Size 中获得该函数的大小,但是您可能还需要其他逻辑来识别给定的地址实际上是一个函数,由增量链接器放置在其上的垫片还是DLL调用重击(同样的事情)。
我想可以通过Linux上的readelf来完成一些类似的工作,但是也许您必须在其源代码之上提出这个库...
您必须记住,尽管可以使用基于反汇编的方法,但是您基本上必须分析一个有向图,其端点在ret,halt,jmp中(已提供增量链接,并且您能够读取jmp -table来识别您在函数中面对的jmp是该函数的内部(图像的jmp-table中缺少)还是外部的(存在于该表中;此类jmp经常作为x64上的尾部调用优化的一部分而发生,因为我知道),任何旨在成为非retret的调用(例如生成异常的助手)等。
答案 15 :(得分:0)
这是一个古老的问题,但仍然...
对于Windows x64,所有函数均具有函数表,该表包含函数的偏移量和大小。 https://docs.microsoft.com/en-us/windows/win32/debug/pe-format。此函数表用于在引发异常时平移。
也就是说,它不包含内联之类的信息以及人们已经注意到的所有其他问题...