获取 C++ 的大小功能
我正在阅读这个问题,因为我正在尝试要在 C++ 程序中查找函数的大小,暗示可能有一种特定于平台的方法。我的目标平台是windows
我目前脑子里的方法如下:
1.获取函数指针
2. 增加指针(和计数器),直到达到 ret
的机器代码值 3.计数器的函数大小是多少?
编辑1:为了澄清我所说的“大小”的含义,我的意思是组成函数的字节数(机器代码)。
编辑2:有一些评论询问为什么或者我打算用这个做什么。诚实的答案是我无意,并且我无法真正看到了解函数长度预编译时间的好处。 (虽然我确信有一些)
这对我来说似乎是一个有效的方法,这行得通吗?
I was reading this question because I'm trying to find the size of a function in a C++ program, It is hinted at that there may be a way that is platform specific. My targeted platform is windows
The method I currently have in my head is the following:
1. Obtain a pointer to the function
2. Increment the Pointer (& counter) until I reach the machine code value for ret
3. The counter will be the size of the function?
Edit1: To clarify what I mean by 'size' I mean the number of bytes (machine code) that make up the function.
Edit2: There have been a few comments asking why or what do I plan to do with this. The honest answer is I have no intention, and I can't really see the benefits of knowing a functions length pre-compile time. (although I'm sure there are some)
This seems like a valid method to me, will this work?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
哇,我一直在使用函数大小计数,它有很多很多用途。可靠吗?决不。它是标准c++吗?决不。但这就是为什么每次发布新版本时都需要在反汇编程序中检查它以确保它有效。编译器标志可能会打乱顺序。
它似乎与静态函数一起工作得更好。全局优化可以杀死它。
PS 我讨厌人们问你为什么要这样做,而这是不可能的,等等。请停止问这些问题。让你听起来很愚蠢。程序员经常被要求做非标准的事情,因为新产品几乎总是突破可用的极限。如果他们不这样做,你的产品可能是对已经完成的事情的重复。无聊的!!!
Wow, I use function size counting all the time and it has lots and lots of uses. Is it reliable? No way. Is it standard c++? No way. But that's why you need to check it in the disassembler to make sure it worked, every time that you release a new version. Compiler flags can mess up the ordering.
It seems to work better with static functions. Global optimizations can kill it.
P.S. I hate people, asking why you want to do this and it's impossible, etc. Stop asking these questions, please. Makes you sound stupid. Programmers are often asked to do non-standard things, because new products almost always push the limits of what's availble. If they don't, your product is probably a rehash of what's already been done. Boring!!!
不,这行不通:
ret
指令。ret
,您也不能只查看各个字节 - 因为相应的值可能仅显示为一个值,而不是一条指令。如果您将编码风格限制为函数中只有一个返回点,那么第一个问题可能可以解决,但另一个问题基本上需要反汇编程序,以便您可以区分各个指令。
No, this will not work:
ret
instruction.ret
, you can't just look at the individual bytes - because the corresponding value could appear as simply a value, rather than an instruction.The first problem can possibly be worked around if you restrict your coding style to, say, only have a single point of return in your function, but the other basically requires a disassembler so you can tell the individual instructions apart.
可以获得函数的所有块,但是询问函数的“大小”是多少是一个不自然的问题。优化的代码将按照执行顺序重新排列代码块,并将很少使用的块(异常路径)移动到模块的外部部分。有关详细信息,请参阅配置文件引导优化,了解 Visual C++ 如何实现的示例这在链接时间代码生成中。因此,函数可以从地址 0x00001000 开始,在 0x00001100 处分支到 0x20001000 处的跳转和 ret,并在 0x20001000 处有一些异常处理代码。在 0x00001110 处,另一个函数启动。您的函数的“大小”是多少?它的范围确实是从 0x00001000 到 +0x20001000,但它只“拥有”该范围内的几个块。所以你的问题不应该被问。
在这种情况下还有其他有效的问题,例如函数的指令总数(可以从程序符号数据库和图像中确定),更重要的是,内部频繁执行的代码路径中的指令数是多少的功能。所有这些都是在性能测量的背景下通常会提出的问题,并且有一些工具可以检测代码并可以给出非常详细的答案。
恐怕在内存中追逐指针并搜索
ret
不会让你有任何结果。现代代码比这复杂得多。It is possible to obtain all blocks of a function, but is an unnatural question to ask what is the 'size' of a function. Optimized code will rearrange code blocks in the order of execution and will move seldom used blocks (exception paths) into outer parts of the module. For more details, see Profile-Guided Optimizations for example how Visual C++ achieves this in link time code generation. So a function can start at address 0x00001000, branch at 0x00001100 into a jump at 0x20001000 and a ret, and have some exception handling code 0x20001000. At 0x00001110 another function starts. What is the 'size' of your function? It does span from 0x00001000 to +0x20001000, but it 'owns' only few blocks in that span. So your question should be unasked.
There are other valid questions in this context, like the total number of instructions a function has (can be determined from the program symbol database and from the image), and more importantly, what is the number of instructions in the frequent executed code path inside the function. All these are questions normally asked in the context of performance measurement and there are tools that instrument code and can give very detailed answers.
Chasing pointers in memory and searching for
ret
will get you nowhere I'm afraid. Modern code is way way way more complex than that.这行不通……如果有一个跳转、一个虚拟
ret
,然后是跳转的目标怎么办?你的代码将会被愚弄。一般来说,以 100% 的准确率不可能做到这一点,因为您必须预测所有代码路径,这就像解决停止问题。如果您实现自己的反汇编程序,您可以获得“相当好的”准确性,但没有任何解决方案会像您想象的那么简单。
一个“技巧”是找出哪个函数的代码在您正在寻找的函数之后,假设某些(危险)假设,这将给出相当好的结果。但是你必须知道你的函数后面有什么函数,这在优化之后很难弄清楚。
编辑 1:
如果函数根本不以 ret 指令结束怎么办?它很可能只是
jmp
返回到它的调用者(尽管不太可能)。编辑 2:
不要忘记 x86 至少有可变长度指令...
更新:
对于那些说流分析与解决停止问题不同的人:
考虑一下当您有如下代码时会发生什么
: 将每次都必须跟随跳转来找出函数的结尾,并且你不能在第一次之后忽略它,因为你不知道是否你正在处理自修改代码。 (例如,您可以在 C++ 代码中使用内联汇编来修改自身。)它很可能扩展到内存的其他位置,因此您的分析器将(或应该)以无限循环结束,除非您容忍误报。
这不是类似于停止问题吗?
This won't work... what if there's a jump, a dummy
ret
, and then the target of the jump? Your code will be fooled.In general, it's impossible to do this with 100% accuracy because you have to predict all code paths, which is like solving the halting problem. You can get "pretty good" accuracy if you implement your own disassembler, but no solution will be nearly as easy as you imagine.
A "trick" would be to find out which function's code is after the function that you're looking for, which would give pretty good results assuming certain (dangerous) assumptions. But then you'd have to know what function comes after your function, which, after optimizations, is pretty hard to figure out.
Edit 1:
What if the function doesn't even end with a
ret
instruction at all? It could very well justjmp
back to its caller (though it's unlikely).Edit 2:
Don't forget that x86, at least, has variable-length instructions...
Update:
For those saying that flow analysis isn't the same as solving the halting problem:
Consider what happens when you have code like:
You will have to follow the jump each time to figure out the end of the function, and you cannot ignore it past the first time because you don't know whether or not you're dealing with self-modifying code. (You could have inline assembly in your C++ code that modifies itself, for instance.) It could very well extend to some other place of memory, so your analyzer will (or should) end in an infinite loop, unless you tolerate false negatives.
Isn't that like the halting problem?
我发这个帖子是为了表达两件事:
1)这里给出的大多数答案都非常糟糕并且很容易崩溃。如果您在可执行文件的
debug
构建中使用C函数指针(使用函数名称),并且可能在其他情况下,它可能会指向JMP
shim 本身不具有函数体。这是一个例子。如果我对下面定义的函数执行以下操作:我得到的
pfn
(例如:0x7FF724241893
)将指向 this,它只是一个JMP< /code> 指令:
此外,编译器可以嵌套其中几个垫片,或对函数代码进行分支,以便它具有多个 epilog 或
ret< /代码> 说明。哎呀,它甚至可能不使用 ret 指令。然后,无法保证函数本身将按照您在源代码中定义的顺序进行编译和链接。
您可以使用汇编语言完成所有这些工作,但不能使用 C 或 C++。
2) 以上是坏消息。好消息是,原始问题的答案是,是的,有一种方法(或黑客)来获取确切的函数大小,但它带有以下内容限制:
它仅适用于 Windows 上的 64 位可执行文件。
它显然是 Microsoft 特定的,并且不可移植。
您必须在运行时执行此操作。
这个概念很简单——利用 x64 Windows 二进制文件中SEH 的实现方式。编译器将每个函数的详细信息添加到 PE32+ 标头(可选标头的 IMAGE_DIRECTORY_ENTRY_EXCEPTION 目录中),您可以使用它来获取准确的函数大小。 (如果您想知道,此信息用于捕获、处理和
__try/__except/__finally
块中展开的异常。)这是一个简单的示例
:然后:
I'm posting this to say two things:
1) Most of the answers given here are really bad and will break easily. If you use the C function pointer (using the function name), in a
debug
build of your executable, and possibly in other circumstances, it may point to aJMP
shim that will not have the function body itself. Here's an example. If I do the following for the function I defined below:the
pfn
I get (for example:0x7FF724241893
) will point to this, which is just aJMP
instruction:Additionally, a compiler can nest several of those shims, or branch your function code so that it will have multiple epilogs, or
ret
instructions. Heck, it may not even use aret
instruction. Then, there's no guarantee that functions themselves will be compiled and linked in the order you define them in the source code.You can do all that stuff in assembly language, but not in C or C++.
2) So that above was the bad news. The good news is that the answer to the original question is, yes, there's a way (or a hack) to get the exact function size, but it comes with the following limitations:
It works in 64-bit executables on Windows only.
It is obviously Microsoft specific and is not portable.
You have to do this at run-time.
The concept is simple -- utilize the way SEH is implemented in x64 Windows binaries. Compiler adds details of each function into the PE32+ header (into the
IMAGE_DIRECTORY_ENTRY_EXCEPTION
directory of the optional header) that you can use to obtain the exact function size. (In case you're wondering, this information is used for catching, handling and unwinding of exceptions in the__try/__except/__finally
blocks.)Here's a quick example:
and then:
这可以在非常有限的情况下工作。我在我编写的代码注入实用程序的一部分中使用它。我不记得在哪里找到这些信息,但我有以下内容(VS2005 中的 C++):
然后在其他一些函数中我有:
您必须关闭一些优化并将函数声明为静态才能使其正常工作;我不记得具体情况了。我不知道这是否是准确的字节数,但它已经足够接近了。大小只是立即函数的大小;它不包括该函数可能调用的任何其他函数。除了像这样的极端情况之外,“函数的大小”是没有意义和无用的。
This can work in very limited scenarios. I use it in part of a code injection utility I wrote. I don't remember where I found the information, but I have the following (C++ in VS2005):
And then in some other function I have:
You have to turn off some optimizations and declare the functions as static to get this to work; I don't recall the specifics. I don't know if this is an exact byte count, but it is close enough. The size is only that of the immediate function; it doesn't include any other functions that may be called by that function. Aside from extreme edge cases like this, "the size of a function" is meaningless and useless.
真正的解决方案是深入研究编译器的文档。我们使用的 ARM 编译器可以生成一个程序集转储 (code.dis),从中减去给定的损坏函数标签和下一个损坏函数标签之间的偏移量是相当简单的。
不过,我不确定 Windows 目标需要哪些工具。看起来这个问题的答案中列出的工具可能就是您正在寻找的工具。
另请注意,我(在嵌入式领域工作)假设您正在谈论编译后分析。仍然可以以编程方式检查这些中间文件作为构建的一部分,前提是:
请注意,我我完全不确定您为什么想知道这些信息。我过去需要它来确保我可以将特定的代码块放入内存中非常特定的位置。我不得不承认我很好奇这对于更通用的桌面操作系统目标有什么用途。
The real solution to this is to dig into your compiler's documentation. The ARM compiler we use can be made to produce an assembly dump (code.dis), from which it's fairly trivial to subtract the offsets between a given mangled function label and the next mangled function label.
I'm not certain which tools you will need for this with a windows target, however. It looks like the tools listed in the answer to this question might be what you're looking for.
Also note that I (working in the embedded space) assumed you were talking about post-compile-analysis. It still might be possible to examine these intermediate files programmatically as part of a build provided that:
Note that I'm not sure entirely WHY you want to know this information. I've needed it in the past to be sure that I can fit a particular chunk of code in a very particular place in memory. I have to admit I'm curious what purpose this would have on a more general desktop-OS target.
在 C++ 中,没有函数大小的概念。除了提到的其他内容之外,预处理器宏还会导致大小不确定。如果你想计算指令字的数量,你不能在 C++ 中这样做,因为它在编译之前不存在。
In C++, the there is no notion of function size. In addition to everything else mentioned, preprocessor macros also make for an indeterminate size. If you want to count number of instruction words, you can't do that in C++, because it doesn't exist until it's been compiled.
“函数的大小”是什么意思?
如果你指的是函数指针,那么对于 32 位系统来说它总是只有 4 个字节。
如果您指的是代码的大小,那么您应该反汇编生成的代码并找到入口点和最近的 ret 调用。一种方法是在函数的开头和结尾处读取指令指针寄存器。
如果您想计算出函数在平均情况下调用的指令数量,您可以使用探查器并将退休指令的数量除以调用数量。
What do you mean "size of a function"?
If you mean a function pointer than it is always just 4 bytes for 32bits systems.
If you mean the size of the code than you should just disassemble generated code and find the entry point and closest
ret
call. One way to do it is to read the instruction pointer register at the beginning and at the end of your function.If you want to figure out the number of instructions called in the average case for your function you can use profilers and divide the number of retired instructions on the number of calls.
我认为它适用于使用 msvc 创建的 Windows 程序,至于分支,“ret”似乎总是出现在末尾(即使有提前返回的分支,它也会执行 jne 来结束)。
但是,您将需要某种反汇编程序库来计算当前操作码长度,因为它们对于 x86 是可变长度的。如果你不这样做,你就会遇到误报。
如果有这种情况没有发生的情况,我不会感到惊讶。
I think it will work on windows programs created with msvc, as for branches the 'ret' seems to always come at the end (even if there are branches that return early it does a jne to go the end).
However you will need some kind of disassembler library to figure the current opcode length as they are variable length for x86. If you don't do this you'll run into false positives.
I would not be surprised if there are cases this doesn't catch.
标准 C++ 中没有工具可以获取函数的大小或长度。
在这里查看我的答案:是否可以将函数加载到某些已分配的内存中并从那里运行它?
一般来说,在嵌入式系统中复制时会使用了解函数的大小可执行代码从只读源(或慢速存储设备,例如串行闪存)到 RAM 中。桌面和其他操作系统使用其他技术(例如动态库或共享库)将函数加载到内存中。
There is no facilities in Standard C++ to obtain the size or length of a function.
See my answer here: Is it possible to load a function into some allocated memory and run it from there?
In general, knowing the size of a function is used in embedded systems when copying executable code from a read-only source (or a slow memory device, such as a serial Flash) into RAM. Desktop and other operating systems load functions into memory using other techniques, such as dynamic or shared libraries.
只需在您获取函数的地址处设置 PAGE_EXECUTE_READWRITE 即可。然后读取每个字节。当你得到字节“0xCC”时,这意味着函数的结尾是actual_reading_address - 1。
Just set PAGE_EXECUTE_READWRITE at the address where you got your function. Then read every byte. When you got byte "0xCC" it means that the end of function is actual_reading_address - 1.
使用 GCC,一点也不难。
Using GCC, not so hard at all.
下面的代码获取准确的功能块大小,它在我的测试中运行良好
runtime_checks 在调试模式下禁用 _RTC_CheckEsp
below code the get the accurate function block size, it works fine with my test
runtime_checks disable _RTC_CheckEsp in debug mode
不可移植但基于 API 且正确工作的方法是使用程序数据库读取器 - 例如 Windows 上的 dbghelp.dll 或 Linux 上的 readelf。仅当调试信息与程序一起启用/存在时,才可以使用它们。下面是一个关于它在 Windows 上如何工作的示例:
您将在 symbol.Size 中获得函数的大小,但您可能还需要额外的逻辑来识别给定的地址是否实际上是一个函数、一个填充程序由增量链接器或 DLL 调用 thunk 放置在那里(相同的事情)。
我想在 Linux 上可以通过 readelf 来完成一些类似的工作,但也许你必须在其源代码之上提供该库......
你必须记住,虽然基于反汇编的方法是可能的,但你基本上会必须分析带有 ret、halt、jmp 端点的有向图(前提是您启用了增量链接,并且您能够读取 jmp 表来确定您在函数中面临的 jmp 是否是该函数的内部(缺少图像的jmp-table)或外部(存在于该表中;据我所知,此类 jmp 经常作为 x64 上的尾部调用优化的一部分出现)),任何本应为 nonret 的调用(例如生成异常的帮助程序)等。
The non-portable, but API-based and correctly working approach is to use program database readers - like dbghelp.dll on Windows or readelf on Linux. The usage of those is only possible if debug info is enabled/present along with the program. Here's an example on how it works on Windows:
You will get the size of the function in symbol.Size, but you may also need additional logic identifying whether the address given is a actually a function, a shim placed there by incremental linker or a DLL call thunk (same thing).
I guess somewhat similar can be done via readelf on Linux, but maybe you'll have to come up with the library on top of its sourcecode...
You must bear in mind that although disassembly-based approach is possible, you'll basically have to analyze a directed graph with endpoints in ret, halt, jmp (PROVIDED you have incremental linking enabled and you're able to read jmp-table to identify whether the jmp you're facing in function is internal to that function (missing in image's jmp-table) or external (present in that table; such jmps frequently occur as part of tail-call optimization on x64, as I know)), any calls that are meant to be nonret (like an exception generating helper), etc.
这是一个老问题,但仍然......
对于 Windows x64,函数都有一个函数表,其中包含函数的偏移量和大小。 https://learn.microsoft.com/en-us/ windows/win32/debug/pe-format 。该函数表用于抛出异常时展开。
也就是说,这不包含内联等信息,以及人们已经注意到的所有其他问题......
It's an old question but still...
For Windows x64, functions all have a function table, which contains the offset and the size of the function. https://learn.microsoft.com/en-us/windows/win32/debug/pe-format . This function table is used for unwinding when an exception is thrown.
That said, this doesn't contain information like inlining, and all the other issues that people already noted...
假设您使用的是 x86 或 x86_64,则可以使用此方法
you could use this assumming you are in x86 or x86_64