0

I am writing a bootloader for an STM32, where I need to jump from bootloader to the real app.

In C this works, because I can cast an address to a void pointer and assign that to a function pointer, and call the function pointer as follows:

void jump_to_firmware(uint32_t address)
{
    uint32_t reset_handler_add = *((volatile uint32_t *)(address + 4));
    void (*app_reset_handler)(void) = (void *)reset_handler_add;

    SCB->VTOR = address;
    uint32_t msp_value = *((volatile uint32_t *)address);
    
    __set_MSP(msp_value);
    app_reset_handler();    
}

If I use the same implementation in a C++ the gnu compiler will give an error on the cast to void pointer.

include/bootloader.hpp:58:39: error: invalid conversion from 'void*' to 'void (*)()' [-fpermissive]

After googling I found this SO page, which I tried and came to the following implementation in my class:

void JumpToApp()
{
    // Quick test if C style cast does work
    //jump_to_firmware(_appStartAddress);

    uint32_t mspAdress = *((volatile uint32_t *)_appStartAddress);
    uint32_t resetHandlerAddress = *((volatile uint32_t *)(_appStartAddress + sizeof(uint32_t)));

    // https://stackoverflow.com/questions/1096341/function-pointers-casting-in-c
    typedef void (*functionPointer)();
    functionPointer resetHandler = 0;
    reinterpret_cast<void*&>(resetHandler) = (void*)resetHandlerAddress;

    SCB->VTOR = _appStartAddress;
    __set_MSP(mspAdress);

    resetHandler();
}

In the C++ implementation:

  • functionPointer resetHandler is assigned with 0x8035065
  • SCB->VTOR is assigned with 0x08020000
  • mspAddress is assigned with `0x20020000
  • then the function pointer resetHandler is called

In the C implementation:

  • app_reset_handler is assigned with 0x8035065
  • SCB->VTOR is assigned with 0x08020000
  • mspAddress is assigned with `0x20020000
  • then the function pointer app_reset_handler is called

The C implementation works, it jumps to my app, the app runs without issues.

The C++ implementation ends up nowhere. It hangs/crashes on the following (to me meaningless) address:

enter image description here

I am trying to keep the amount of source files to a minimum, so I would like to keep the logic in the single class definition.

My questions:

  • Did I misunderstand the linked SO page and can somebody see where I went wrong in the C++ implementation?
  • Is there a better/easier way to cast an address to a function pointer in C++?
  • Or is there a technical reason why it simply can't be done in C++?

PS: The bootloader code is the same in both cases. The only difference I made to test either implementation is to comment out the code in Bootloader::JumpToApp and call the C function jump_to_firmware(uint32_t) instead.

PPS: all peripherals are deinitialized properly. Again, it works fine, the problem only appears when I use this C++ implementation.

bas
  • 13,550
  • 20
  • 69
  • 146
  • In the C there is a "+4" that I don't see in the C++. I may be missing something, but could that be why it's jumping to the wrong address? – psmears Jun 30 '21 at 20:38
  • 1
    Oh this pointer-arithmetic-by-hand stuff is nuts. It looks like you start with the address of the vector table, stored in some integer. So `auto vect_table = reinterpret_cast(_appStartAddress)` and now you can read the other things you need via `vect_table[0]` and `vect_table[1]`. In both C and C++, except C uses a C-style cast instead of `reinterpret_cast`. – Ben Voigt Jun 30 '21 at 20:39
  • Side note: `uintptr_t` should be a better fit than `uint32_t`. Probably won't matter now, but should ease porting to a 64 bit CPU in the future. – user4581301 Jun 30 '21 at 20:41
  • So `auto resetHandler = reinterpret_cast(vect_table[1]);` takes care of one. And `__set_MSP(vect_table[0]);` is the other. – Ben Voigt Jun 30 '21 at 20:41
  • @user4581301: Vector table layout is very much architecture-specific, there's no portable way to write code that accesses it. Might be worth something akin to `static_assert(ARM && !ARM64)` – Ben Voigt Jun 30 '21 at 20:42
  • @BenVoigt thx!! I am processing your remarks! I'll be busy on that for a bit :) – bas Jun 30 '21 at 20:43
  • Pedantically, you possibly also want `extern "C"` in `typedef void (*functionPointer)();` so that it is a pointer to a C-linkage function. Unlikely to matter in practice. – Ben Voigt Jun 30 '21 at 20:49
  • @BenVoigt Good point. – user4581301 Jun 30 '21 at 20:49
  • @BenVoigt the problem is that code will not compile anymore as C. There is a better way – 0___________ Jun 30 '21 at 20:53
  • 1
    @0___________: My suggestion works just fine in C if you use C-style casts instead of `reinterpret_cast`. I don't know why you would think otherwise. – Ben Voigt Jun 30 '21 at 20:58
  • 1. The odd addres of the reset handler looks a bit ....odd to me. Are you sure it is correct? 2. Apparently 'the same' call is performed differently in C and in C++. Have you tried to compile the code to assembler language and compare how the call is made in both cases? 3. Maybe the variable pointing at the jump destination should be declared as `extern "C"` ....? Of course the `extern "C"` declaration needs to be conditional under `#ifdef _cplusplus`... – CiaPan Jun 30 '21 at 21:01
  • I am lost. It works with either solution now..... The BenVoigt approach works. The 0_____ approach works. Even my initial implementation works..... Maybe I caused some issue with uploading the bootloader which wiped my app and started to chase ghosts. – bas Jun 30 '21 at 21:17
  • @CiaPan: Function pointers on ARM are a bit weird in that the low bit is not part of the address, but the mode (ARM vs Thumb) that the processor must use for the function being called. – Ben Voigt Jul 01 '21 at 02:02
  • @BenVoigt OMG you are brilliant. It took me (longer than I should I guess) a while, but now I understand your "table" trick. It simplifies *a lot* !! Rewriting some parts where I read/write to flash. I am a fan now – bas Jul 05 '21 at 12:09

1 Answers1

2

The same code will compile in C and C++. You simple has to cast to the correct cast (in C++ you cant assign a void * to non void * pointer. It is much more strict than in C.

void jump_to_firmware(uint32_t address)
{
    uint32_t reset_handler_add = *((volatile uint32_t *)(address + 4));
    void (*app_reset_handler)(void) = (void (*)(void))reset_handler_add;
    /* ... */ 
}

If you do not like those weird casts you can typedef the function.

typedef void handler(void);

void jump_to_firmware(uint32_t address)
{
    uint32_t reset_handler_add = *((volatile uint32_t *)(address + 4));
    handler *app_reset_handler = (handler *)reset_handler_add;
    /* ... */ 
}
0___________
  • 60,014
  • 4
  • 34
  • 74
  • 3
    Just say no to `*((volatile uint32_t *)(address + 4))`, write `((volatile uint32_t *)address)[1]`. And the function pointer typedef suggested in the question is a really good idea and works fine in C as well. – Ben Voigt Jun 30 '21 at 20:55
  • @BenVoigt What if the offset from the base address is 6? It is not good idea. OP did it OK and there is nothing to improve. – 0___________ Jun 30 '21 at 21:05
  • This works too. I think the C-style casts are easier to process for my limited brain. Thx both for your suggestions. I will re-read this tomorrow after some sleep... – bas Jun 30 '21 at 21:19
  • 1
    @0___________ : The thing he's accessing is a **table**. When accessing a table from C or C++ you can use array subscripting. – Ben Voigt Jul 01 '21 at 02:01