2

Basically, I am trying to find a reliable way to get the length of a C function in bytes.

A few people suggest using the address of the function and the address of another function directly following it. I don't want to use this method, as I don't believe it is reliable. The compiler could move things around, and it just seems a bit hack-ish.

The solution I have come up with is to use two labels at the start and the end of a function. I have declared the lables with:

__asm__ __volatile__("func_start/end:");

Now, my problem is, I am having trouble getting the address of these labels put back into a variable in C.

Here is some code that I have already tried:

unsigned int  addr, len;

__asm__ __volatile__("mov  $func_start, %[address]\n\t"\
                    "mov  $func_end, %[length]\n\t"\
                    : [address]"=r"(addr), [length]"=r"(len));


__asm__ __volatile__("mov  %0, $func_start \n\t"\
                     "mov  %1, $func_end\n\t"\
                    : "=r"(addr), "=r"(len));

Both of these examples give the error "ARM register expected" during compilation. As I am not very experienced with ARM assembly (let alone x86), I'm not sure what's wrong. I have tried to port the following x86 code to ARM:

__asm__ __volatile__("movl  $enc_start, %0\n\t"\
                    "movl  $enc_end, %1\n\t"\
                    : "=r"(addr), "=r"(len));

I'm obviously doing something wrong here. I would greatly appreciate it if someone could explain what I'm doing wrong. I've been stuck on this for a few weeks now. Thanks!

eswick
  • 519
  • 4
  • 16
  • Just throwing out an alternative idea here: since ARM processor have fixed instruction widths (16 or 32 bits depending on the mode) you could calculate the length of a function by scanning for `BX LR`, `MOV PC,LR` or an `LDM` which has `PC` in its register list. For example, a `BX LR` instruction would be `0xE12FFF1E` in ARM mode and `0x4770` in Thumb mode (IIRC). Of course this approach would only work if your function has a single exit point. – Michael Jun 25 '13 at 18:55
  • @Michael - it's not a given that the exit point of a function (even singular) is at the end - I was able to quickly find a counterexample where an internal loop followed the exit point by objdumping an arm Android ndk library I built. Also, there are more return possibilities - turns out my example does it by popping the program counter off the stack. – Chris Stratton Jun 25 '13 at 19:27
  • @ChrisStratton: Well, I mentioned `LDM`, which is really what `POP` is on ARM. But yeah, it's probably far from a bullet-proof solution. – Michael Jun 25 '13 at 19:33
  • `gcc` does generate function sizes and this information is used to allow stack traces when some different compiler options are used. The Linux kernel uses this on the ARM to implement stack traces and profiling, etc. I am no expert on getting this information, but it exists. I gave up and used the 2nd function approach; I needed to copy a routine from SDRAM to another area when the SDRAM controller needs to be re-initialized in a boot loader. – artless noise Jun 25 '13 at 21:59
  • @artlessnoise while gcc generates _function sizes_ (ELF symbols have both value and size - the value of a `FUNC` is its address, and the size, well ... its size), this information is not used to allow stacktraces (the also compiler-generated unwind section information is used for that - it contains _frame sizes_). – FrankH. Jun 26 '13 at 10:24
  • @FrankH. I think there are both. Ie, `.fnstart`, `.fnend`, `.save` is for *UNWIND*, and `.type name,func_foo` and `.size`. See: [When are elf directives needed](http://stackoverflow.com/questions/4423211/when-are-elf-directives-needed). I think that the 2nd type might be of interest to the OP. I would be pretty sure that *PC address* is looked up in the frame to find a bound range (address/size) to print out a routine name. If the stack dump only includes addresses, then you may be correct. But at least on my device, it dumps **ascii** routine names. – artless noise Jun 26 '13 at 17:55
  • @FrankH. Also, [ARM Linx and frame pointer](http://stackoverflow.com/questions/15752188/arm-link-register-and-frame-pointer) may clear up what I am talking about? An *UNWIND* is fine for exception recovery, but usually for a stack trace you wish to include code aspects. – artless noise Jun 26 '13 at 17:59
  • 1
    @artlessnoise Two ways to backtrace: If there are framepointers, just iterate the linked list of `{FP,LR}` pairs that's on the stack. Without framepointers, you need func start/end/_framesize_ (from unwind, as you say): start at `{SP,PC}`, find `PC` in the `.fnstart`,`.fnend` tables, then `SP` -> `SP - .frsz`, `PC` -> `LR`, `LR` -> `*(SP)`. unwind alone does _not identify the function_ for you; the unwind table merely says "code in this range: framesize ...". No mapping to func name from unwind only. Backtraces showing names must also inspect the _symbol table_ to retrieve this information. – FrankH. Jun 27 '13 at 08:57

1 Answers1

4

There is no reliable way to get the length of a function because there is no well-defined meaning for length of a function.

A C implementation may in-line a function. It may both in-line the function in some places and provide a non-lined version in another. The function might have code preceding its entry point. The function might have code following its exit point. A function might have multiple exit points. Other routines called might be in-lined inside the function. The function might share code with other functions (e.g., two functions that do the same thing except that one performs some slight manipulations on its parameters before beginning might be given two separate entry points to mostly common code). A function might have some code in one place and other code in another place, with code for other functions in between. A function, even if written as a single sequence of code by the compiler, might be broken into blocks and rearranged by the linker. (OS X does this.)

If you wish to persist in spite of the above, you can try:

#include <stdio.h>


static void foo(void)
{
    __asm__ __volatile__("_foo_start:");
    printf("Hello, world.\n");
    __asm__ __volatile__("_foo_end:");
}


int main(void)
{
    extern const char foo_start[], foo_end[];
    printf("Difference = 0x%tx.\n", foo_end - foo_start);
    foo();
    return 0;
}

It is, of course, not supported by the C standard.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • do you need to manually mangle the name (`_` prefix) if you add a `.global` directive? Personally, I'd declare the variables as incomplete array types - it doesn't make the hack any less horrible, but it somehow 'feels' more appropriate... – Christoph Jun 25 '13 at 19:50
  • @Christoph: The tools I am working with (OS X) do require that names in assembly be prefixed with underscore if they are to match unprefixed names in C. I agree, incomplete array types might be more aesthetic. – Eric Postpischil Jun 25 '13 at 19:52
  • The `foo()` function will not work if the function returns a parameter. Also, the label `_foo_end` will typically be emitted before function epilogue. You must use the `attribute naked` and now a 2nd function shouldn't seem so *hackish* when you implement all of this. An external function from a separate compilation unit should have a defined size in 'C'; this is more an aspect of the linker. – artless noise Jun 25 '13 at 21:53