是否可以将 .text 部分自动拆分到多个内存区域?

Is it possible to auto split the .text section across mulitple memory areas?

我目前正在为具有拆分闪存区域的微控制器编写嵌入式程序。像

MEMORY { 
    flash1  : ORIGIN = 0x1000, LENGTH = 0x1000
    /* 1K gap */
    flash2  : ORIGIN = 0x3000, LENGTH = 0x1000
}

我的应用程序已经发展到 ".text" 部分大于 flash1 的区域。 是否可以在两个区域中自动拆分文本部分?

类似于:

SECTIONS {
   .text0 : { *(.text) } > flash1
   .text1 : { *(.text) } > flash2  
}

如果我正在做上面那样的事情。我收到一条错误消息,告诉我

ld: a.out section `.text' will not fit in region `flash1'
ld: region `flash1' overflowed by 240 bytes

我知道我可以做类似的事情:

SECTIONS {
   .text0 : { mylargefile (.text) } > flash1
   .text1 : { *(.text) }            > flash2  
}

但我没有一个大文件。所以问题是是否可以告诉链接器填充第一个输出部分,直到它已满,然后继续下一部分?

更多信息:

我没有提供完整的示例,因为我的实际应用程序比我复杂得多,尽管使用 LMA 和 VMA 部分以及一些特殊文件的特殊部分。但是这个答案应该能为我提供足够的帮助来继续或提出更多问题。

我期待着答案。我希望我的问题在没有完整样本的情况下足够清楚

编辑1: 问题 的解决方案在我的情况下不正确,因为该解决方案使用通配符手动溢出文件。我问是否有自动解决方案。否则,每次对代码库进行较大更改时,我都将被迫手动调整链接器文件。

编辑2: 更改示例以确保存在 Tom V

所指出的差距

好吧,如果第一个部分已满,一些编译器允许 link 程序(和脚本)溢出到另一个部分。

但这些有时也有问题,这取决于部分何时满的顺序以及何时切换,它不会回来填满第一个,例如你在开头或中间有很大一部分。当 linker 由于溢出现在跳到下一节时,第一节可能刚填满一半。它不会在那个大的部分之后使用较小的部分,回到第一部分来填写。

所以,似乎 GNU LD、Gold-linker 以及 LLVM/CLANG linker(在他们的站点上声明 linker 脚本和GNU LD是一样的,exceptions page没有说明),不支持overflow into another section

所以,为了让它更容易,也许有一些提示:

  • 随着时间的推移,按文件名模式过滤会变得乏味。而且,它不允许您按符号过滤,只能按文件名模式和输入部分名称过滤。

  • 如果你有像 AUTOSAR MemMap 这样的方法,你可以通过将它们放入新的部分来拆分代码,这些部分的命名与默认 .text, .rodata, .data, .bss 不同。 (不幸的是,GCC + CLANG 使用 attribute(( )) 而不是 C-standard 的 #pragma 或 _Pragma() 方式。 (AUTOSAR MemMap例子在最后)

    • 你可以分成快速和慢速代码,例如引入新的部分,如 .codefast, .codeslow(例如 ISR 代码快,任务级代码“慢”)
    • 您可以分成 QM 和 ASIL 分区,引入像 .code_qm, .code_asila, .code_asilb
    • 这样的部分
    • 您可以拆分代码和配置/常量数据,将配置放入单独的可刷新部分,例如.text, .rodata 部分可以拆分为 .text, .const .postbuildconfig, .calib .. 使用部分不同的编码风格,您可以在这里保持代码相同,但项目配置(例如 CAN 配置、过滤器链等)可以使用可配置的配置,您只需刷新一个新配置,而无需更新代码本身
      • .text --> 闪存扇区1
      • .postbuildconfig --> 闪存扇区2
      • .calib --> 闪存扇区 3
  • 在实际开始真正编译和 link 之前,也许中间构建阶段可以使用 objdump / nm / size / readelf 之类的工具首先扫描 .o 文件以提供输出 object / 部分大小并根据特定标准通过脚本对其进行总结,例如输入内存布局和上述特殊部分或按大小排序和 .o 文件名模式,您可以使用它来更新 linker 脚本以适应内存定义。所以,脚本可以尝试适应直到间隙尽可能多,然后切换到另一个内存。它甚至可以尝试生成稍后传递给 linker 的 linker 脚本部分。您可以在 perl 或 python.

    中编写这样的 linker 预处理脚本

连同到不同部分的内存映射,您现在可以像这样过滤:

MEMORY {
    // Maybe you have two 128k areas separate by a gap
    // Put the code in the first big part, and the more
    // "configurable part" in the second area might even 
    // allow you to flash/update the separately 
    CODE1     : origin = 0x10000, len = 128k
    // maybe here is a gap
    CODE2     : origin = 0x10000, len = 64k
    CONST     : origin = 0x20000, len = 4k
    CALIB     : origin = 0x40000, len = 4k
    POSTBUILD : origin = 0x50000, len = 48k
}
SECTIONS {
    .code1 : {
        *(.text)       // default .text
        *(.code_fast)  // fast/slow code split
        *(.code_qm)    // qm/asil code split
    } > CODE1
    .code2 : {
        *(.code_slow)  // fast/slow code split
        *(.code_asila) // qm/asil code split
        *(.code_asilb) // qm/asil code split
    } > CODE2
    .const : {
        *(.const)
        *(.rodata)
    } > CALIB
    
    .calib : {
        *(.calib)
        *_calib.o(.calib) // in case you separate them also out into xxx_Calib.c files compiled to xxx_Calib.o files
    } > CALIB
    
    .postbuild : {
         *(.postbuild)
         *_PBCfg.o(.postbuild) // in case you separate them also out into xxx_PBCfg.c files compiled to xxx_PBCfg.o files
     } > POSTBUILD
}

这种方法还允许您在 header 中准备布局,并通过某些外部工具生成配置和校准。

此外,它还可以更好地估计您的内存资源消耗,因此也可以估算如何以及在何处将它们 place/link 到内存部分。

这是代码中的 AUTOSAR MemMap 使用示例

#define XXX_START_SEC_CODE_FAST
#include "MemMap.h"

void XXX_Channel0_Notification(void) {
    // Channel0 Finished Notification e.g. called from ISR
}

#define XXX_STOP_SEC_CODE_FAST
#include "MemMap.h"

MemMap.h 可以这样配置:

#if defined(XXX_START_SEC_CODE_FAST)
#undef XXX_START_SEC_CODE_FAST
// open section .codefast
#pragma section code ".codefast"

#elif defined(XXX_START_SEC_CODE_SLOW)
#undef XXX_START_SEC_CODE_SLOW
// open section .codeslow
#pragma section code ".codeslow"
#endif

#if defined(XXX_STOP_SEC_CODE_FAST)
#undef XXX_STOP_SEC_CODE_FAST
// back to default section
#pragma section code 

#elif defined(XXX_STOP_SEC_CODE_SLOW)
#undef XXX_STOP_SEC_CODE_SLOW
// back to default section
#pragma section code 
#endif

可以生成MemMap.h文件,通过扫描所有start/stop部分定义,并通过配置工具生成pragmas(甚至可以是Excel)。

优点是,您不会溢出所有可能的编译指示(例如不同的编译器 <-> 不同的方言),或者映射或不映射。因此,如果可能的话,您实际上可以在不同的项目中重用代码。