28

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?

Community
  • 1
  • 1
Adam
  • 707
  • 1
  • 7
  • 15
  • 5
    Uncertain but if a function has multiple returns. Doesn't that mean you're getting flawed results ? – Khez Apr 13 '11 at 21:08
  • 2
    What if there is more than one `ret`? What if the function is inlined? What if the function has sub-functions that have been inlined into it? – Oliver Charlesworth Apr 13 '11 at 21:09
  • 1
    Bad idea. What exactly are you trying to archive? – BЈовић Apr 13 '11 at 21:09
  • @Adam: By the way, C++ is no better than C in this manner, if not worse. – user541686 Apr 13 '11 at 21:15
  • this question is so weird that it must be for hack/exploit code. From that point of view I refuse to entertain this question. – Someone Somewhere Apr 13 '11 at 21:20
  • 1
    @Someone Somewhere, I can assure you it is for educational purposes only. – Adam Apr 13 '11 at 21:24
  • 12
    It's sad to see so many negative responses to a question that could simply be in the pursuit of hard-to-obtain knowledge about computer architecture. We should give the OP the benefit of the doubt. – Michael Chinen Apr 13 '11 at 21:25
  • 1
    @fsmc: There have been a lot of comments/answers pointing out the pitfalls, but few (only one or two, as far as I can tell) that just bash the question. I actually expected it to get downvotes and/or close votes, but as far as I can see none of those. – Michael Burr Apr 13 '11 at 22:31
  • @Michael at the time of my comment, there were three of six non-OP comments saying this is a bad idea, malicious, or pointless. Not understanding the point of something and asking is common and useful for SO, but the tone of these were definitely disapproving. – Michael Chinen Apr 13 '11 at 23:07
  • @Micheal: fsmc is correct, the majority of the early comments were definitely of that form, and I got the same tone from them as well. But I did get some useful answers (Specifically Micheal Madsen's & dsmc's), I think I'm going to look into some disassembly libraries and see if I can find a dependable method. ;) – Adam Apr 13 '11 at 23:15
  • Possible duplicate: http://stackoverflow.com/questions/3569877/is-it-possible-to-load-a-function-into-some-allocated-memory-and-run-it-from-ther/3569999#3569999 – Thomas Matthews Apr 13 '11 at 23:27
  • 1
    @fsmc & Adam: Have some of those comments been deleted? For the record, I don't think the comments indicating that this is a bad idea or asking why you'd want to do this are necessarily negative. Often someone asks a question about a particular approach to solving a problem, when what they really should ask is what approach should be taken. Sometimes it can be hard to determine from a question whether it's 'academic', or if it's really about some unstated goal. Granted, some of the comments might be curt, but that doesn't mean they were intended to be negative. Certainly not abusive, right? – Michael Burr Apr 14 '11 at 00:14
  • 3
    @Michael:Indicating that something is a bad idea AND not understanding the point or purpose (implied by asking 'why do this' after calling it a bad idea) at the same time is suggesting that it is pointless. The point of such a comment is clearly not to determine whether it is academic or not. Assuming that something curious must be malicious is also not so nice. There are many ways to ask why someone would want to do something. A curt example would be "what are you doing this for?" but the questions here are longer and suggest the question has no useful point. – Michael Chinen Apr 14 '11 at 11:24
  • What happens if I declare a function and never call it. Does that function take memory or does compiler just ignore it? ( C, C++ ) – Thinkal VB Jan 30 '20 at 09:26

17 Answers17

21

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.

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;
}

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!!!

Jordan
  • 453
  • 4
  • 6
  • 1
    Hi Jordan, thank you for this clever (and working!) way to accomplish the task. I actually succeeded without a second static function, just by putting the `0xCCCCCC` directly on the main function. I had to manually clean-up the stack via ASM directly. `__asm MOV ESP, EBP __asm POP EBP __asm RETN` Furthermore to have the exact length of the function without the placeholder I changed ++length to length++ – Wizche Mar 27 '14 at 15:44
  • Loved the idea, thank you! – Nox Oct 02 '22 at 13:09
15

No, this will not work:

  1. There is no guarantee that your function only contains a single ret instruction.
  2. Even if it only does contain a single 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.

Michael Madsen
  • 54,231
  • 8
  • 72
  • 83
  • 2
    The function will never be inlined if somewhere else in the program, he requests a function pointer to it. Agreed on the "This is generally not possible / difficult" notion though. – Ana Betts Apr 13 '11 at 21:17
  • +1: I did consider that the function could be inline, which isn't the case. However, I didn't consider multiple `ret`s or the possible of an arg having the corresponding opcode value – Adam Apr 13 '11 at 21:20
  • 3
    In addition, there's nothing that says that the compiled output for a function needs to be contiguous. Especially when optimizing, the compiler (and linker) will place blocks of code all over the place (before the function, maybe in the middle of another function), to try to avoid branches in code that it believes to be the 'hot' path, to place jump targets at particular alignments, or to coalesce identical instruction sequences (or whatever). The point being that there's yet one more thing that makes this a fools errand. – Michael Burr Apr 13 '11 at 21:22
  • 3
    Following the sense of Michael's comment, it need not even contain a `ret` line at all. GCC sometimes does optimizations where it simply jumps into another function/block that contains the `ret` already. I suppose if it exits only with exceptions it might not have a return either. – edA-qa mort-ora-y Apr 13 '11 at 21:26
  • 4
    Even if you modify your C/C++ coding style to have only one return per function, there's no guarantee that the compiler/optimizer emitted assembly code that does the same! – Drew Hall Apr 13 '11 at 22:16
  • @Paul: Ah yes, of course - my mistake. I've removed that bit again. @Drew: Hence the word "possibly". :) – Michael Madsen Apr 13 '11 at 22:36
  • 2
    In addition to all the great arguments already raised, no one has mentioned that, in variable-width instruction sets *(like x86)*, it is **not even possible to reliably disassemble code** without running it. For example, consider the assembly `jmp Cont; db 01; Cont: ...` That is, insert the byte `0x01` between the `jmp` statement and the next statement. When you are stepping through, there is no way for you to know that the `0x01` *isn't* the start of the next instruction, so everything disassembled after that point will be garbage. This is why it's so hard to write a disassembler for x86. – BlueRaja - Danny Pflughoeft Apr 13 '11 at 22:54
13

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.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • 1
    Btw, this is what I do to see the 'size' of a function: load the module in Windbg, then `uf module!functioname`. `uf` does a pretty good job at full function disassembly even when dealing with code shred to pieces by a modern generation/optimization tool, and I can see the number of instructions, blocks etc. – Remus Rusanu Apr 13 '11 at 23:12
7

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 just jmp 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:

foo:
    ....
    jmp foo

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?

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    It's plenty possible to do this. You don't need to prove anything about whether a given branch is reachable -- which is what you'd need to solve the halting problem for. Enumerating the possible code paths however is flow analysis, which is very much do-able. – Billy ONeal Apr 13 '11 at 21:14
  • Basically you'd calculate this the same way cyclomatic complexity is calculated. – Billy ONeal Apr 13 '11 at 21:16
  • @Billy: You need to prove that a certain piece of code is *not* reachable though, don't you? That seems pretty similar to the halting problem to me... – user541686 Apr 13 '11 at 21:16
  • @Mehrdad: Nope -- you are only counting code size, you only need to do flow analysis. Even if the code is not reachable it's still part of the size of the function. You don't care if the code is reachable, you only care if the code exists. – Billy ONeal Apr 13 '11 at 21:18
  • @Billy: But how do you distinguish that it's code and not data? If it's not reachable, is it dead code or is it data?? You have to prove it's data in order to exclude it from the function (or prove that it's code in order to include it), and either one is impossible. – user541686 Apr 13 '11 at 21:19
  • @Mehrdad: I don't understand why you'd have to do that. A function starts with a label and ends with a `ret`. We have the start, so it's perfectly possible to enumerate all the possible paths that end in `ret` s. Then one could remove portions of those paths which are the same, and sum the remaining path byte lengths. It takes potentially exponential time (in extremely rare cases), but it's not incomputeable. (I agree with you that the OP shouldn't try to do this, but *impossible* is a strong word. – Billy ONeal Apr 13 '11 at 21:24
  • Oh, and when I say "possible paths" I mean "potentially reachable paths" -- i.e. one does not try to evaluate a branch instruction, one merely notes that a branch instruction exists. – Billy ONeal Apr 13 '11 at 21:27
  • @Billy: What if you have a `jmp` instead of a `ret` though? No one said it has to end with a `ret`... it could jump to a register value, possibly the caller, or possibly to the code immediately following itself. So where does your function end exactly? – user541686 Apr 13 '11 at 21:28
  • @Mehrdad: I know of no compilers which will use a `jmp` to return. While it may seem a sensible thing because of the difference in semantics between the two instructions, it's generally a bad idea on x86 because it often breaks the branch predictor. – Billy ONeal Apr 13 '11 at 21:37
  • @Billy: You don't need to have a compiler that does that, just use `__declspec(naked)` and inline assembly (or the equivalent in your compiler). – user541686 Apr 13 '11 at 21:39
  • @Billy: Also consider that polymorphic jumps are indirect jumps. What if the compiler happens to optimize by using `jmp` instead of `ret`, and just let the virtual method do a single return instead? When is the "end" of your function in that case? – user541686 Apr 13 '11 at 21:43
  • @Mehrdad: I'm assuming the OP is working with sensible code. If one doesn't use a `ret` then yes, this problem becomes hard. I'd still argue not impossible but I can't come up with an algorithm off the top of my head. In response to your added bit to your answer, my response is to look at how cyclomatic complexity is calculated -- because the algorithm to do cyclomatic complexity is the same thing I describe above. Your loop example is handled by not allowing a given path form a loop. – Billy ONeal Apr 13 '11 at 21:44
  • @Billy: `Your loop example is handled by not allowing a given path form a loop.` Why? There's no reason not to. If you could determine whether a piece of code was reached, then you're effectively solving the halting problem. Also, see my last comment about polymorphic jumps, which are much more "sensible" and quite realistic. – user541686 Apr 13 '11 at 21:45
  • @Mehrdad: My point is not that this is not a hard or impractical problem -- it is hard and impractical. But it is not **impossible**. – Billy ONeal Apr 13 '11 at 21:47
  • @Mehrdad: Because you don't care if it forms a loop. You only want the function length. If you ever see a loop you know you've already counted the size of the inside of the loop, so you can disregard it. (i.e. you can ignore jumps which jump *back* in the function because you only want the end of it). – Billy ONeal Apr 13 '11 at 21:48
  • @Billy: How do you define the word "function"? If you characterize it by a `ret`, then you can never know when you'll reach one unless you follow each and every loop... – user541686 Apr 13 '11 at 21:52
  • @Mehrdad: No, you don't. A given function may not have a linear code path, but in memory it does have a beginning and an end. If you have a jump in that function forward, you know that everything between the jump and the jump target is part of the function. If you have a jump backward, you already know that area is part of the function, so it can be ignored. Look at cyclomatic complexity. It solves a much harder problem because it enumerates every possible branch (because that's what cyclomatic complexity is -- number of possible paths through a function) – Billy ONeal Apr 13 '11 at 21:56
  • @Billy: *But you're ignoring the possibility of indirect jumps!* If your code never modifies itself, then that would work. But indirect jumps -- like which happens in polymorphism -- makes this impossible, since you have no idea where to `jmp` to, and no idea where to continue looking for a `ret`. – user541686 Apr 13 '11 at 21:57
  • http://en.wikipedia.org/wiki/Cyclomatic_complexity <-- Every major IDE I've heard of does this correctly. I find it hard to believe something's impossible when not only does everyone do it, they do it fast enough to do it in real time on edited code (in most cases). – Billy ONeal Apr 13 '11 at 21:58
  • @Billy: I know what cyclomatic complexity is, but what does it have to do with this? You're still not telling me how you handle the polymorphism `jmp` issue, if your compiler optimizes out the `ret`s from the caller. – user541686 Apr 13 '11 at 21:59
  • @Mehrdad: Calculation of cyclomatic complexity finds the end of the function (after all -- it's finding the number of linearly independent paths through that function). – Billy ONeal Apr 13 '11 at 22:02
  • @Billy: You're *still* not telling me how the polymorphism issue is resolved, you just seem to be completely ignoring it... :( – user541686 Apr 13 '11 at 22:19
  • +1 Whoops, I didn't see this when I wrote [my comment above](http://stackoverflow.com/questions/5655624/getting-the-size-of-a-c-function/5655706#5655706) *(@Billy, @Mehrdad is correct, see my comment)* – BlueRaja - Danny Pflughoeft Apr 13 '11 at 22:58
  • @Mehrdad: Any indirect call is a function call, and therefore wouldn't enter the analysis. – Billy ONeal Apr 14 '11 at 05:05
  • @BlueRaja: Not true. As I explained already, one would not have to follow paths which jump backwards. It would be *hard* and *impractical* to do, but not *impossible* to do. I'm not saying one can just scan for a `ret`, nor am I saying that the OP will be able to implement such a thing. I *am* saying that this is not a *provably incomputable* problem. (If you can produce a proof I would be happy to stand corrected, but saying "I think it's like the halting problem" or "here are some things that make it hard" do not prove that it is incomputable.) – Billy ONeal Apr 14 '11 at 05:06
  • Both of you: The proof for the halting problem assumes the existence of a program which does not halt. Here, we do not care if the program halts. We do not care if a branch is reachable. We are not doing anything more than flow analysis like that which is implemented in most any compiler. (Flow analysis has to deal with things like indirect calls and nasty instructions too) The proposed problem therefore is not similar to the halting problem at all. If you can show a reduction, be my guest, but until then there's no reason to speculate. – Billy ONeal Apr 14 '11 at 05:10
  • @Billy: It's not an indirect `call`, it's an indirect `jmp`. As far as I know, that's not a function call now, is it? (Also: Did you take a look at @BlueRaja's comment about disassembling x86 and how it can be *impossible*?) – user541686 Apr 14 '11 at 05:10
  • @Billy: It is indeed true, and I already showed some code which cannot be reliably disassembled (and doesn't need to jump backwards). To see this, simply replace `jmp` with `jnz`; that is, `jnz Cont; db 01; Cont: ...` The question is, is the byte `0x01` the beginning of an instruction, or is the instruction pointed at by the `jnz` the beginning of the instruction? Assuming `0x01` is not an opcode by itself (it's not), and assuming only one path will always be taken, the only way to know the answer is to know which path will be taken; (continued..) – BlueRaja - Danny Pflughoeft Apr 14 '11 at 05:16
  • @Billy: (continued..) that is, to know if the computation before the `jnz` (which could be anything) = 0. Since determining the output of an arbitrary program is equivalent to the halting problem, there is no way to reliably answer that question. – BlueRaja - Danny Pflughoeft Apr 14 '11 at 05:17
  • @BlueRaja: You don't need to disassemble. You can follow the code just as the processor does. – Billy ONeal Apr 14 '11 at 16:12
  • 1
    @Billy: I'm sorry, could you explain a bit more? *"Following the code just as the processor does"* is called running the program. Aside from the fact that running the program to determine the return value from one function is a terribly inefficient idea, there is no guarantee that the program will even *call* that function during that run. And you cannot simply start running the program beginning at the function in question, because you don't know what sort of input values the function is expecting - not just function parameters, but also global variable states. – BlueRaja - Danny Pflughoeft Apr 14 '11 at 16:20
  • @BlueRaja: No, it is not running the program, because you are not considering branches. You're merely finding the farthest potential branch. You don't care if the branch is taken or not taken, you only care if it is there. If a branch potentially jumps into the middle of another instruction than your analysis could reflect that. (i.e. so you follow the branch both ways) – Billy ONeal Apr 14 '11 at 16:50
  • 1
    @Billy: But that is what I'm saying - even if you know the function has only one return point, if the `jnz` jumps into the middle of an instruction, you will have two completely different code paths with (potentially) two different `ret` statements. How do you know which one is the correct `ret` *(note that this will not always be the "farthest `ret`")*? The only way to know is to know if the `jnz` will jump or not, which is an undecidable problem. – BlueRaja - Danny Pflughoeft Apr 14 '11 at 16:59
4

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 a JMP shim that will not have the function body itself. Here's an example. If I do the following for the function I defined below:

FARPROC pfn = (FARPROC)some_function_with_possibility_to_get_its_size_at_runtime;

the pfn I get (for example: 0x7FF724241893) will point to this, which is just a JMP instruction:

enter image description here

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 a ret 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:

//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);

and then:

#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;
    }
}
ahmd0
  • 16,633
  • 33
  • 137
  • 233
3

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):

#pragma runtime_checks("", off)

static DWORD WINAPI InjectionProc(LPVOID lpvParameter)
{
    // do something
    return 0;
}

static DWORD WINAPI InjectionProcEnd()
{
    return 0;
}

#pragma runtime_checks("", on)

And then in some other function I have:

size_t cbInjectionProc = (size_t)InjectionProcEnd - (size_t)InjectionProc;

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.

Luke
  • 11,211
  • 2
  • 27
  • 38
  • Two corrections and one warning here: 1) I would add `__declspec(noinline)` to your method declarations to prevent inlining in a `release` build. 2) It should be `#pragma runtime_checks("", restore)` instead of `on` 3) **A warning:** If you plan injecting this method into a separate process, this will crash it if your `InjectionProc` method has any statically linked API calls. – ahmd0 Jun 20 '16 at 07:04
2

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:

  • The target function is in a different object
  • The build system has been taught the dependencies
  • You know for sure that the compiler will build these object files

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.

Community
  • 1
  • 1
jkerian
  • 16,497
  • 3
  • 46
  • 59
1

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.

Elalfer
  • 5,312
  • 20
  • 25
1

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.

Zach Rattner
  • 20,745
  • 9
  • 59
  • 82
  • That was the point of using a pointer. (no pun intended) My goal is to get the number of bytes, which is the machine code, that make up the function in memory. – Adam Apr 13 '11 at 21:14
1

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.

Michael Chinen
  • 17,737
  • 5
  • 33
  • 45
1

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.

Community
  • 1
  • 1
Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
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.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
MrOMGWTF
  • 29
  • 1
0

below code the get the accurate function block size, it works fine with my test runtime_checks disable _RTC_CheckEsp in debug mode

    #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;
};
Shen Tony
  • 19
  • 3
0

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:

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);

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.

Arty
  • 723
  • 5
  • 10
0

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...

atlaste
  • 30,418
  • 3
  • 57
  • 87
0
int GetFuncSizeX86(unsigned char* Func)
{
    if (!Func)
    {
        printf("x86Helper : Function Ptr NULL\n");
        return 0;
    }

    for (int count = 0; ; count++)
    {
        if (Func[count] == 0xC3)
        {
            unsigned char prevInstruc = *(Func - 1);
            if (Func[1] == 0xCC // int3
                || prevInstruc == 0x5D//  pop    ebp
                || prevInstruc == 0x5B//  pop    ebx
                || prevInstruc == 0x5E//  pop    esi
                || prevInstruc == 0x5F//  pop    edi
                || prevInstruc == 0xCC//  int3
                || prevInstruc == 0xC9)// leave
                return count++;
        }
    }
}

you could use this assumming you are in x86 or x86_64

Jôsùå
  • 1
  • 1
  • 3
  • Hi Josua, welcome. This may work for your code (have you tested it ? what compiler ?), but as a general answer, it is *very* compiler dependent.. and I could ask a lot of "what about" questions: What about a return halfway ? what if compiler behaviour is changed in future versions, you'd have to revise and test with every update. What if a return is done using a jump first.. – Goodies Aug 21 '21 at 00:04
  • i used that, on msvc of visual studio, and clang compiler, you are right, the code is very simple! – Jôsùå Aug 22 '21 at 22:07
  • Have a look at the third answer here. Jordan uses the same method (pick up the assembly and scan it) but Jordan's code is independent of platform *and* independent on the location of a return (0xC3) statement in the function assembly. You don't know, if there are multiple ret (0xC3) statements in the assemby.. It may work for your code, but it won't work for all *legal* functions. I see that as the most important issue in your solution. – Goodies Aug 22 '21 at 22:30
  • you are totally right, next time I will evaluate what my code can do in the context of other problems, before uploading! – Jôsùå Aug 23 '21 at 09:53
0

Using GCC, not so hard at all.

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));
  • 3
    Is this guaranteed to be accurate? I didn't think that the compiler guaranteed anything about ordering of instructions within a function. – Yuliy Jan 08 '12 at 17:49
  • First of all it seems you need a statement after the label, and secondly this doesn't work if you need `&&do_something_END` to be available at file scope, making it more or less useless. – Thomas Apr 18 '15 at 06:27