-2

I am writing a function:

void callFunctionAt(uint32_t address){
  //There is a void at address, how do I run it?
}

This is in Atmel Studio's C++. If previous questions are to be believed, the simple answer is to write the line "address();". This cannot be correct. Without changing the header of this function, how would one call the function located at the address given?

The answer should be system-agnostic for all micro controllers which support standard c++ compilation.

tuskiomi
  • 190
  • 1
  • 15
  • I don't understand what you want to do. When I read topic I tought you asked about "where is functions stored" but when I read rest of it I was confused. – dunajski Feb 06 '20 at 05:40
  • @dunajski I know where the function is stored. How do I run it only based on that location? – tuskiomi Feb 06 '20 at 05:43
  • 1
    the optiboot.h wraps a call to do_spm function in bootloader. See if you can extract from it what you want https://github.com/Optiboot/optiboot/blob/master/optiboot/examples/test_dospm/optiboot.h – Juraj Feb 06 '20 at 05:53
  • Is this an AVR based MCU or not? Please fix your tags or even better simply state the target MCU in the question, as pointed out in an answer AVR is a special beast that is difficult at best to do this, others it aint no thing. – old_timer Feb 06 '20 at 15:55
  • The answer cannot be system-agnostic for all micro controllers which support standard c++ compilation, you need to remove that requirement. That is not how the embedded world works. – old_timer Feb 06 '20 at 15:56
  • @old_timer I'm inclined to disagree based on current answers – tuskiomi Feb 07 '20 at 05:04
  • @tuskiomi its an experience thing, and keep reading those answers, if you already knew the answer then why ask? The AVR vs ARM question plays a big role in what is possible/practical/portable. Did you answer that yet? Then there is the why would you use C++ on a platform like this, etc. With these rules/assumptions of yours you are going to limit where your code can run, vs take advantage of the width and breadth of products out there. I see this happen quite often. – old_timer Feb 07 '20 at 14:58

3 Answers3

4

The common way to do this is to give the argument the correct type. Then you can call it right away:

void callFunctionAt(void (*address)()) {
  address();
}

However, since you wrote "Without changing the header of this function [...]", you need to cast the unsigned integer to a function pointer:

void callFunctionAt(uint32_t address) {
  void (*f)() = reinterpret_cast<void (*f)()>(address);
  f();
}

But this is not safe and not portabel because it assumes that the uint32_t can be casted into a function pointer. And this needs not to be true: "[...] system-agnostic for all micro controllers [...]". Function pointers can have other widths than 32 bits. Pointers in general might consist of more than the pure address, for example include a selector for memory spaces, depending on the system's architecture.


If you got the address from a linker script, you might have declared it like this:

extern const uint32_t ext_func;

And like to use it so:

callFunctionAt(ext_func);

But you can change the declaration into:

extern void ext_func();

And call it directly or indirectly:

ext_func();

callFunctionAt(&ext_func);

The definition in the linker can stay as it is, because the linker knows nothing about types.

the busybee
  • 10,755
  • 3
  • 13
  • 30
  • you only moved the problem out of the callFuctionAt. the question is how to have callFuctionAt with a parameter of type uint32_t containing the address. so show OP how to cast the address to a function – Juraj Feb 06 '20 at 09:22
  • Thanks, @Juraj, I added some more thoughts. -- BTW, your example in the question's comment does *not* solve the problem. It shifts the address an as integer down one more level. – the busybee Feb 06 '20 at 10:16
  • This is a very interesting. Thank you for the answer. Very useful – tuskiomi Feb 07 '20 at 05:10
3

There is no generic way. It depends on which compiler you are using. In the following I'll assume avr-g++ because it's common and freely available.

Spoiler: On AVR, it's more complicated than on most other machines.

Suppose you actually have a uint32_t address which would be a byte address. Function pointers in avr-g++ are word addresses actually, where a word has 16 bits. Hence, you'll have to divide the byte address by 2 first to get a word address; then cast it to a function pointer and call it:

#include <stdint.h>

typedef void (*func_t)(void);

void callFunctionAt (uint32_t byte_address)
{
    func_t func = (func_t) (byte_address >> 1);
    func();
}

If you started with a word address, then you can call it without further ado:

void callFunctionAt (uint32_t address)
{
    ((func_t) word_address)();
}

This will only work for devices with up to 128KiB of flash memory! The reason is that addresses in avr-g++ are 16 bits long, cf. the layout of void* as per avr-gcc ABI. This means using scalar addresses on devices with flash > 128KiB will not work in general, for example when you issue callFunctionAt (0x30000) on an ATmega2560.

On such devices, the 16-bit address in Z register used by EICALL instruction is extended by the value held in the EIND special function register, and you must not change EIND after entering main. The avr-g++ documentation is clear about that.

The crucial point here is how you are getting the address. First, in order to call and pass it around properly, use a function pointer:

typedef void (*func_t)(void);

void callFunctionAt (func_t address)
{
    address();
}

void func (void);

void call_func()
{
    func_t addr = func;
    callFunctionAt (addr);
}

I am using void argument in the declaration because this is how you'd do it in C. Or, if you don't like the typedef:

void callFunctionAt (void (*address)(void))
{
    address();
}

void func (void);

void call_func ()
{
    void (*addr)(void) = func;
    callFunctionAt (addr);
}

If you want to call a function at a specific word address like, for example 0x0 to "reset"1 the µC, you could

void call_0x0()
{
    callFunctionAt ((func_t) 0x0);
}

but whether this works depends on where your vector table is located, or more specifically, how EIND was initialized by the startup code. What will always work is using a symbol and define it with -Wl,--defsym,func=0 when linking with the following code:

extern "C" void func();

void call_func ()
{
    void (*addr)(void) = func;
    callFunctionAt (addr);
}

The big difference compared to using 0x0 directly it that the compiler will wrap symbol func with symbol modifier gs which it will not do when using 0x0 directly:

_Z9call_funcv:
    ldi r24,lo8(gs(func))
    ldi r25,hi8(gs(func))
    jmp _Z14callFunctionAtPFvvE

This is needed if the address is out of the scope of EIJMP to advise the linker to generate a stub.

1 This will not reset the hardware. The best approach to force a reset is by letting the watchdog timer (WDT) issue a reset for you.


Methods

Yet another situation is when you want the address of a non-static method of a class because you also need a this pointer in that case:

class A
{
    int a = 1;
public:
    int method1 () { return a += 1; }
    int method2 () { return a += 2; }
};

void callFunctionAt (A *b, int (A::*f)())
{
    A a;
    (a.*f)();
    (b->*f)();
}

void call_method ()
{
    A a;
    callFunctionAt (&a, &A::method1);
    callFunctionAt (&a, &A::method2);
}

The 2nd argument of callFunctionAt specifies which method (of a given prototype) you want, but you also need an object (or pointer to one) to apply it. avr-g++ will use gs when taking the method's address (provided the following call(s) cannot be inlined), thus it will also work for all AVR devices.

emacs drives me nuts
  • 2,785
  • 13
  • 23
0

Based on comments I think you are asking about how microcontroller calls function. Could you compile your program to see assembly files? I would recommend you to read one of them.
Every function after compiling are translated to instructions that CPU can do (loading to register, adding to register etc.).
So then your void foo(int x) {statements;} compile to simple CPU instructions and whenever you call foo(x) in your program, you are moving to instructions that are related to foo - you are calling a subroutine.
As far as I remeber there is a CALL function in AVR to invoke subroutines and the name of subroutine is the label where executing program jump and invoking next instruction at adress. I think you can clarify your doubts when you read some AVR assembly tutorials.
It is fun (at least for me) to see what exactly CPU do when it calls function that I wrote, but it required to know what instructions do. You develop in AVR so there is a set of instructions that you can read about in this PDF and compare with your assembly files.

dunajski
  • 381
  • 3
  • 15