【问题标题】:Code sharing between multiple independently compiled binaries/hex files多个独立编译的二进制/十六进制文件之间的代码共享
【发布时间】:2019-03-29 13:17:57
【问题描述】:

我正在寻找有关如何在为 Cortex-m/0/4/7 架构编译的多个二进制文件之间共享信息/代码的文档/信息。这两个二进制文件将位于相同的芯片和相同的架构上。它们在不同的位置闪烁并设置主堆栈指针并重置程序计数器,以便一个二进制“跳转”到另一个二进制。我想在这两个二进制文件之间共享代码。

我已将函数指针数组简单地复制到链接描述文件中定义的部分到 RAM 中。然后在另一个二进制文件中读取 RAM 并将其转换为一个数组,然后使用索引调用另一个二进制文件中的函数。这确实可以作为概念验证,但我认为我正在寻找的是更复杂的东西。因为我想要某种方式来描述两个二进制文件之间的兼容性。我想要一些共享库的功能,但我不确定是否需要位置无关的代码。

作为一个例子,当前的复制过程是如何完成的:

源码二进制:

void copy_func()
{
   memncpy(array_of_function_pointers, fixed_size, address_custom_ram_section)
}

也从源二进制跳转的二进制:

array_fp_type get_funcs()
{
   memncpy(adress_custom_ram_section, fixed_size, array_of_fp)
   return array_of_fp;
}

然后我可以使用array_of_fp 从跳转二进制文件调用驻留在源二进制文件中的函数。

因此,我正在寻找一些资源或输入,以供已实施类似系统的人使用。就像我不想有一个自定义 RAM 部分,我将函数指针复制到其中。

我可以让源二进制文件的编译步骤输出一些可以包含在跳转二进制文件的编译步骤中的东西。但是,它必须是可重现的,并且只要您不更改接口,重新编译源二进制文件不应破坏与跳转二进制文件的兼容性(即使它包含与现在输出的文件不同的文件)。

澄清源二进制文件不需要任何关于跳转二进制文件的特定知识。代码不应驻留在两个二进制文件中,因为这会破坏此机制的目的。如果这种机制是在 cortex-m 处理器上创建多二进制应用程序时节省空间的一种方式,那么总体目标是。

欢迎任何想法或资源链接。如果您还有其他问题,请随时对问题发表评论,我会尽力回答。

【问题讨论】:

  • 我不太明白,也许您只是想将第一个程序编译为库并编译第二个程序,包括第一个库?也许解释一下你为什么想做这样的事情,以便我们提出解决方案
  • 所以我希望源二进制文件作为程序运行(可以编译为库,但我不想将其编译为跳转二进制文件),因为要求两者都可以单独编程他们共享一些资源。所以在某种程度上,你有例如。带有网络堆栈(很大)的引导加载程序我想在引导加载程序和使用它的应用程序之间共享网络堆栈。引导加载程序不了解应用程序,但应用程序了解引导加载程序。这是否清除了@Julien?所以我不想使用源代码二进制文件中的代码。
  • 这实际上与 dll 或 .so 文件的工作方式没有什么不同,听起来您在正确的轨道上,您需要某种数据结构来识别函数或变量及其在编译后的二进制文件,将其传递给辅助程序,它需要使用函数指针等连接到另一个二进制文件。这不是一个被无数次解决的新问题,与 mcus 或 cortex-m 无关......如果我理解这个问题。
  • 很清楚,谢谢。今晚我将为您的使用提供解决方案。但是,我们可以想象一个应用程序 + 一个依赖于相同动态库的引导加载程序。 app 和 boot 依赖于 lib,app 可以改变而不影响 lib 或 boot。这合适吗?
  • 执行此操作的方法可能是特定于工具链的,因此您可能需要指定工具链。但本质上,您创建了一个函数指针表,并通过工具链支持的任何方式安排该表,以将该表定位在为此目的而保留的已知地址处。然后第二个二进制文件可以访问该表以调用函数。

标签: embedded cortex-m cortex-m3


【解决方案1】:

我很难想象你想要做什么,但如果你有兴趣在你的引导加载程序/ROM 上建立一个应用程序链接,请参阅Loading symbol file while linking 以获取有关你可以做什么的提示。

构建您的“源”(?)图像,抓取它的映射文件并制作一个符号文件,然后在链接您的“跳转”(?)图像时使用它。

这确实意味着您需要将“跳转”图像与“源”图像的特定版本相关联。

如果您需要它们是半版本独立的(即您定义了一组可以导出的函数,但您可以在任一侧重新构建),那么您需要在“源”图像中的已知位置导出函数指针并链接到您的“跳转”图像中的那些函数指针。您可以通过使函数指针结构访问任一侧的函数来简化簿记。

例如:

shared_functions.h:

struct FunctionPointerTable
{
  void(*function1)(int);
  void(*function2)(char);
};

extern struct FunctionPointerTable sharedFunctions;

“源”图像中的源文件:

void function1Implementation(int a) 
{
    printf("You sent me an integer:  %d\r\n", a);

    function2Implementation((char)(a%256)) 
    sharedFunctions.function2((char)(a%256));
}

void function2Implementation(char b) 
{
    printf("You sent me an char:  %c\r\n", b);
}

struct FunctionPointerTable sharedFunctions = 
{
    function1Implementation,
    function2Implementation,
};

“跳转”图片中的源文件:

#include "shared_functions.h"

sharedFunctions.function1(1024);
sharedFunctions.function2(100);

当您编译/链接“源”时,获取其映射文件并提取 sharedFunctions 的位置并创建与源链接“跳转”图像的符号文件。

注意:printfs(或由共享函数直接调用的任何内容)将来自“源”图像(而不是“跳转”图像)。

如果您需要它们来自“跳转”图像(或被覆盖),那么您需要通过相同的函数指针表访问它们,并且“跳转”图像需要使用它的函数指针表来修复相关功能的版本。我更新了 function1() 以显示这一点。对 function2 的直接调用将始终是“源”版本。它的共享函数调用版本将通过跳转表并调用“源”版本,除非“跳转”图像更新函数表以指向其实现。

你可以摆脱结构,但是你需要一个一个地导出函数指针(不是一个大问题),但是你想保持它们的顺序和在一个固定的位置,这意味着明确地把它们放在链接器描述符文件等。我展示了将其提炼为最简单示例的结构方法。

正如你所看到的,事情变得相当棘手,并且有一些惩罚(通过函数指针调用更慢,因为你需要加载要跳转到的地址)

【讨论】:

    【解决方案2】:

    正如评论中所解释的,我们可以想象一个应用程序和一个引导加载程序依赖于同一个动态库。因此应用程序和引导加载程序依赖于库,可以更改应用程序而不影响库或引导。

    我没有找到一种简单的方法来使用 arm-none-eabi-gcc 创建一个共享库。然而 this document 提供了一些共享库的替代方案。我的情况是,我会推荐跳转表解决方案。

    编写一个包含需要在引导加载程序和应用程序中使用的函数的库。

    “库”代码

    typedef void (*genericFunctionPointer)(void)
    
    // use the linker script to set MySection at a known address
    // I think this could be a structure like Russ Schultz solution but struct may or may not compile identically in lib and boot. However yes struct would be much easyer and avoiding many function pointer cast. 
    const genericFunctionPointer FpointerArray[] __attribute__ ((section ("MySection")))=
    {
         (genericFunctionPointer)lib_f1,
         (genericFunctionPointer)lib_f2,
    }
    
    void lib_f1(void)
    {
         //some code
    }
    
    uint8_t lib_f2(uint8_t param)
    {
         //some code
    }
    

    应用程序和/或引导加载程序代码

    typedef void (*genericFunctionPointer)(void)
    
    // Use the linker script to set MySection at same address as library was compiled
    // in linker script also put this section as `NOLOAD` because it is init by library and not by our code
    //volatile is needed here because you read in flash memory and compiler may initialyse usage of this array to NULL pointers
    volatile const genericFunctionPointer FpointerArray[NB_F] __attribute__ ((section ("MySection")));
    
    enum 
    {
        lib_f1,
        lib_f2,
        NB_F,
    
    }
    
    int main(void)
    {
        (correctCastF1)(FpointerArray[lib_f1])();
        uint8_t a = (correctCastF2)(FpointerArray[lib_f2])(10);
    }
    

    【讨论】:

      【解决方案3】:

      您可以研究使用链接器部分。如果您在 bootloader 文件夹中有您的引导加载程序源代码,则可以使用

      SECTIONS
      {
          .bootloader:
          {
              build_output/bootloader/*.o(.text)
          } >flash_region1
      
          .binary1: 
          {
              build_output/binary1/*.o(.text)
          } >flash_region2
      
          .binary2:
          {
              build_output/binary2/*.o(.text)
          } >flash_region3 
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-24
        • 2017-02-20
        • 1970-01-01
        • 2014-10-30
        • 1970-01-01
        • 2013-12-20
        • 2016-04-17
        相关资源
        最近更新 更多