0

I have two separate projects that rely on the same external dependency in an embedded environment. I would want to share the code of that dependency between the projects.

So far I split those functions off into their own section via linker script. After linking one project I can have a map of all functions (with the -Map option in ld), but now I can't link the second project against this.

I thought of having a script that parses the map file and generates a header to define all the functions as explicit offsets by parsing the original headers of the external dependency (e.g. typedef int (*external_add_t)(int, int); external_add_t external_add = (external_add_t)(0x8dff000000); ) then add the results as headers in the second project.

I'm really hoping there's a better way to do that. Anyone tried something similar?

Example:

project1/
  src/project1.c
  project1.ldS
  external_lib (submodule with CMake, static dependency)
  CMakeLists.txt

project2/
  src/project2.c
  project2.ldS
  external_lib (submodule with CMake, static dependency)
  CMakeLists.txt

external_lib/
  src/external_add.c
  include/external_add.h
#include "external_add.h"

void project_entry_point() {
    // This part is different between project1 and project2
    const uint32_t param = 1;

    external_add(param, 2);  // But this is called from both
}

project1 is compiled and the resulting binary has 0x00008d7fe0089000 external_add, project2 is compiled and the resulting binary has 0x00008d7fe0089008 external_add. The desired result is to get project2 to reuse the same external_add produced by the project1 binary.

Wanderer
  • 3
  • 3
  • I don't quite understand what you are trying to do. Why can't you use the same source code files and the same linker script setup? Done. – Lundin Mar 28 '23 at 13:21
  • I'm not explicitly defining the offsets for those functions. Using the same source code for the external dependency, these functions will end up linked in different offsets by the two projects. – Wanderer Mar 28 '23 at 13:25
  • Hence "use the same linker script" and create custom flash sections. But only if the actual memory layout matters. – Lundin Mar 28 '23 at 13:26
  • The external project has many functions, and I need the functions themselves to end up with the same offsets. I have this currently in the first project: ``` .text.external : { PROVIDE(_text_external_start = .); *external/*(.text*) . = ALIGN(8); PROVIDE(_text_external_end = .); } > rom ``` But using this in the second project won't mean the external functions will end up with the same offsets. – Wanderer Mar 28 '23 at 13:31
  • Read my previous comment. – Lundin Mar 28 '23 at 13:39
  • I did... Why would using the same linker script help ensure the functions end up at the same offset, even if I ensure all the obj's text sections are at the same offset? – Wanderer Mar 28 '23 at 14:25
  • Because of the mentioned custom memory segments, created in the linker script and referred to in the source - which is done in some compiler/linker specific way. In case of gcc-flavoured tool chains then `__attribute__((section(".my_memory")))`. However as mentioned in an answer, it might be sufficient just to allocate function pointers in a custom segment and then from there don't care where the functions get allocated. Because maintaining functions that constantly change size would otherwise be a pain in the neck. – Lundin Mar 28 '23 at 14:29
  • Adding the attribute for the particular section will ensure it ends up in that section, but I mentioned that I'm already doing that in the initial question. That doesn't mean each function will be in a consistent offset, which is what I would need to use it from two projects in the same address space. If I defined `SETIONS { . = 0x800; .ext : {...}}` and have the same functions compiled twice with `__attribute__((section(".ext")))` in different projects, they will not necessarily end up with the same offset after 0x800. I hope that clarifies what I mean. – Wanderer Mar 28 '23 at 14:35
  • Why do you care about some offset? Just allocate them at absolute addresses. Or are the projects for entirely different MCUs? Anyway, this starts to sound more and more like an "XY question", you might be looking for an implementation of the wrong solution Y in order to solve problem X, instead of finding the best solution to solve problem X. Bootloaders is nothing new and there's countless examples of how to write them. – Lundin Mar 28 '23 at 14:40
  • (There is something called "Position Independent Code" mostly relevant to asm, but can be used in C too. However, that too is likely the wrong solution to your actual problem.) – Lundin Mar 28 '23 at 14:40
  • > Just allocate them at absolute addresses Allocate the functions at absolute addresses? You mean by adding a section with an explicit address in the linker script for _each function_? I'm trying to find what it is I did not communicate, but the problem I have that there's no going around is that the linker will dynamically determine some function offsets and I will need to get these same function offsets in a project that is compiled afterwards and separately. – Wanderer Mar 28 '23 at 14:43
  • Yes for each function, or else how can you know where they are. However, the answer with a function pointer LUT is a more flexible work-around. – Lundin Mar 28 '23 at 14:46

2 Answers2

2

As I understand you want to call functions (for example located in the bootloader) from other code on the same micro. Do not place functions at specific addresses (or the section - but the section will have to be placed at some defined address. You can also store the address of this section in one fixed location) only the pointers to them.

Then in the project which defines those functions have this section (containing pointers to functions) as load and in the second project as noload. Then you will be able to call functions in one project via the function pointers.

Example if function pointers are 32 bits.

uint32t __attribute__((section(".myfuncvectors")) myfuncvectors[] = 
{
    (uint32_t)func1,
    (uint32_t)func2, 
    /*....*/

I use this method in many of my embedded projects

0___________
  • 60,014
  • 4
  • 34
  • 74
  • I'm not sure I know what you mean. Do you mean adding a PLT-like section in the second project and modifying it with the pointers from the first project? – Wanderer Mar 28 '23 at 13:37
  • Why to modify? I do not understand. As I understand you have some code from one project and code from another project in the same memory. Projects are different but you want to call some functions in project one from project 2 – 0___________ Mar 28 '23 at 13:39
  • That's true, yes. How are you generating the pointers to the functions in order to be able to use them from project 2? – Wanderer Mar 28 '23 at 14:26
  • It _could_ be helpful to provide a [mre] showing the method. – the busybee Mar 28 '23 at 14:52
  • @Wanderer you have an example – 0___________ Mar 28 '23 at 14:59
  • I added a scenario to the main question, hope it makes it clearer. – Wanderer Mar 28 '23 at 16:14
0

You may be interested in this mechanism: https://stackoverflow.com/a/495631/550235

Basically, scrape project1 to get the symbol addresses you want, generate the symdef file, then pass that to the second link.

NOTE: you don't compile the 'shared' code or link with the 'shared' object on the 2nd project. That's what the symdef file provides.

I've most used this mechanism for linking to a ROM which is absolutely inviolate.

DOUBLE NOTE: You need to be absolutely sure nothing you're sharing is resolving anything outside of itself. Literal pools, globals, statics, etc. all can inadvertently result in code generation differences. Probably the best way to prove that is build project1 and project2 using the same linker file and compare the binary for the section you're interested in.

Russ Schultz
  • 2,545
  • 20
  • 22