13

I have some firmware built with GCC that runs on an ARM Cortex M0 based microcontroller. The build currently generates a single binary image that can be written into the program memory of the microcontroller.

For reasons to do with field update, I need to split this image into two parts that can be updated separately. I'll call these Core and App.

  • Core: contains the interrupt vector table, main() routine, and various drivers and library routines. It will be located in the first half of the program memory.

  • App: contains application-specific code. It will be located in the second half of the program memory. It will have a single entry point, at a known address, which is called by the core to start the application. It will access functions and data in the core via known addresses.

There are some obvious limitations here, which I'm well aware of:

  • When building the app, the addresses of symbols in the core will need to be known. So the core must be built first, and must be available when linking the app.

  • An app image will only be compatible with the specific core image it was built against.

  • It will be possible to update the app without updating the core, but not vice versa.

All of that is OK.

My question is simply, how can I build these images using GCC and the GNU binutils?

Essentially I want to build the core like a normal firmware image, and then build the app image, with the app treating the core like a library. But neither shared linking (which would require a dynamic linking mechanism) or static linking (which would copy the core functions used into the app binary) are applicable here. What I'm trying to do is actually a lot simpler: link against an existing binary using its known, fixed addresses. It's just not clear to me how to do so with the tools.

Martin L
  • 1,016
  • 9
  • 12
  • Define all __Core__ exports in the linker script fragment (easily automated) and `INCLUDE` this fragment into an __App__ linker script. – user58697 Feb 03 '16 at 19:23
  • Any reason why you can't have x number of C projects, that all share the common core files with each other, then build x completely separate binaries depending on application, all of them including both core+app? The alternative seems overly complicated. Also, this way you can ensure that the app is always in sync with the core, in case the core needs to be changed at some point. – Lundin Feb 04 '16 at 09:08
  • @user58697: That seems workable. I'm surprised that doing this requires external scripting though? It seems like something that should be possible with existing functionality in binutils. – Martin L Feb 04 '16 at 12:28
  • @Lundin I'm not actually interested in having multiple separate projects running on this platform. I just have a single project that needs to be split into two parts due to technical constraints around the field update process. – Martin L Feb 04 '16 at 12:28
  • I've found [this question](http://stackoverflow.com/questions/31676890/linking-binary-against-functions-data-in-specific-location-in-memory/) and [this followup](http://stackoverflow.com/questions/33207402/how-to-link-against-just-symbols-correctly) asking more or less the same thing. Using `objcopy` to generate a symbol file for the core and passing this to `ld` with `--just-symbols` seems part of the solution, but was still leaving undefined reference errors. – Martin L Feb 04 '16 at 13:25
  • For any random IDE, you should be able to add the same files to multiple projects. The IDE may or may not support having multiple projects open at once. – Lundin Feb 04 '16 at 13:31
  • @Lundin I'm sorry but I don't think you've understood the question. – Martin L Feb 04 '16 at 14:27
  • I understand it, I just question how such requirements make sense. It seems you are facing the choice between a simple, robust solution with one single binary and a complicated, messy solution with two binaries in the same processor, which will involve rolling out your own flash programming drivers, bootloaders etc, and also seems like a nightmare to maintain in case the two binaries turn out incompatible somewhere along the way. – Lundin Feb 04 '16 at 15:36
  • 1
    @Lundin The central issue is that there isn't enough memory on the hardware to store a single monolithic image whilst it is being received and verified, prior to being written to the program area. Receiving an update uses complex communications code that is also used by the app, so sharing is essential, and a simple bootloader that receives and flashes incrementally is not an option here as connectivity may be lost. Splitting the image has been selected as the best way to meet the requirements. Everyone is well aware of the constraints it imposes. – Martin L Feb 04 '16 at 16:26
  • `ld --just-symbols` can consume object files, so I don't think you need to use `objcopy` to generate a symbol file. – claymation Dec 05 '21 at 22:02

2 Answers2

9

We have this working now so I am going to answer my own question. Here is what was necessary to do this, starting from a normal single image build, turning that into the "core" and then setting up the build for the "app".

  1. Decide how to split up both the flash and the RAM into separate areas for the core and the app. Define the start address and size of each area.

  2. Create a linker script for the core. This will be the same as the standard linker script for the platform except that it must only use the areas reserved for the core. This can be done by changing the ORIGIN and LENGTH of the flash & RAM entries in the MEMORY section of the linker script.

  3. Create a header file declaring the entry point for the app. This just needs a prototype e.g.:

void app_init(void);.

  1. Include this header from the core C code and have the core call app_init() to start the app.

  2. Create a symbol file declaring the address of the entry point, which will be the start address of the flash area for the app. I'll call this app.sym. It can just be one line in the following format:

app_init = 0x00010000;

  1. Build the core, using the core linker script and adding --just-symbols=app.sym to the linker parameters to give the address of app_init. Retain the ELF file from the build, which I'll call core.elf.

  2. Create a linker script for the app. This will again be based on the standard linker script for the platform, but with the flash & RAM memory ranges changed to those reserved for the app. Additionally, it will need a special section to ensure that app_init is placed at the start of the app flash area, before the rest of the code in the .text section:

SECTIONS
{
    .text :
    {
        KEEP(*(.app_init))
        *(.text*)
  1. Write the app_init function. This will need to be in assembly, as it must do some low level work before any C code in the app can be called. It will need to be marked with .section .app_init so that the linker puts it in the correct place at the start of the app flash area. The app_init function needs to:

    1. Populate variables in the app's .data section with initial values from flash.
    2. Set variables in the app's .bss section to zero.
    3. Call the C entry point for the app, which I'll call app_start().
  2. Write the app_start() function that starts the app.

  3. Build the app, using the app linker script. This link step should be passed the object files containing app_init, app_start, and any code called by app_start that is not already in the core. The linker parameter --just-symbols=core.elf should be passed to link functions in the core by their addresses. Additionally, -nostartfiles should be passed to leave out the normal C runtime startup code.

It took a while to figure all this out but it is now working nicely.

Martin L
  • 1,016
  • 9
  • 12
  • I'm doing something similar, but would like to export a limited set of symbols from "core" to "app", to ensure the application doesn't access objects that haven't been designed with concurrency in mind (in my case, the two executables run concurrently on different processors). I've tried using a version script to do that, with no success. Have you explored this idea at al? – claymation Dec 07 '21 at 03:10
  • I wonder why the format of the symbol file created by objdump is different from the one used by gcc? – Gábor Aug 16 '23 at 15:53
0

First of all... if this is just for field updating, you don't need to rely on the interrupt vector table in the core space for the app. I think ARM M0 parts always have the ability to move it. I know it can be done on some (all?) the STM32Fx stuff, but I believe this is an ARM M-x thing, not an ST thing. Look into this before committing yourself to the decision to make your application ISRs all be hooks called from the core.

If you plan on having a lot of interaction with your core (btw, I always call the piece that does self-updating a "bootloader" on MCUs), here's an alternate suggestion:

Have the Core pass a pointer to a struct / table of functions that describes its capabilities into the App entry point?

This would allow complete separation of the code for the app vs core except for a shared header (assuming your ABI doesn't change) and prevent name collisions.

It also provides a reasonable way to prevent GCC from optimizing away any functions that you might call only from the App without messing up your optimization settings or screwing around with pragmas.

core.h:

struct core_functions
{
    int (*pcore_func1)(int a, int b);
    void (*pcore_func2)(void);
};

core.c:

int core_func1(int a, int b){ return a + b; }
void core_func2(void){ // do something here }

static const struct core_functions cfuncs= 
{
    core_func1,
    core_func2
};

void core_main()
{
   // do setup here
   void (app_entry*)(const struct core_functions *) = ENTRY_POINT;
   app_entry( &cfuncs );
}

app.c

void app_main(const struct core_functions * core)
{
   int res;
   res = core->pcore_func1(20, 30);
}

The downside / cost is a slight runtime & memory overhead and more code.

Brian McFarland
  • 9,052
  • 6
  • 38
  • 56
  • Thanks for your answer. Firstly though, I'm afraid this is exactly the approach I wanted to avoid. There are a lot of functions to be exported so maintaining this would be a pain, on top of the overhead involved. It could be scripted/generated, but at that point I'd be better off with the suggestion in @user58697's comment above. This is a linker issue - it shouldn't have to be done in the code. Secondly, the reason I didn't use the term bootloader is that there's actually a bootloader before all this for direct programming. The core handles a more complex remote update process. – Martin L Feb 04 '16 at 12:47