This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] TMS570LS1225:HALCoGen 输出不符合 EABI 的代码、这会破坏 GCC

Guru**** 663810 points
Other Parts Discussed in Thread: HALCOGEN, TMS570LS1225, TMS570LS3137
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/965783/tms570ls1225-halcogen-outputs-non-eabi-compliant-code-which-can-break-gcc

器件型号:TMS570LS1225
主题中讨论的其它部件:HALCOGENTMS570LS3137

大家好!

我一直在尝试(并且成功)使 HALCoGen 输出可使用 ARM GCC 工具链编译的代码(不使用 CCS -不确定这是否会影响 CCS/Linaro),并偶然发现了 HALCoGen 输出的一部分问题:

sys_startup.c 中启动序列的一部分:

/* sourceId:startup_sourceId_001 */
/* DesignId:startup_DesignId_001 */
/*要求:HL_SR508 */
void _c_int00 (void)
{    
/*用户代码开始(5)*/
/*用户代码结束*/

   /*初始化内核寄存器以避免 CCM 错误*/
   _coreInitRegisters_();

这里的问题是_coreInitRegisters_()不符合 EABI (它不能兼容)、并且可以避免使用堆块化寄存器 GCC。 这就产生了以下情况:

00000f88 <_c_int00>:
    f88:      e3e04000       mvn    R4、#0
    f8c:      ebfffc5d       BL     108 <_coreInitRegisters_>
    f90:      ebfffc8a       BL     1C0 <_coreInitStackPointer_>
    f94:      ebfffca4       BL     22c <_coreEnableEventBusExport_>
    f98:      ebfffd0e       BL     3d8 <_errata_CORTEXR4_66_>
    f9c:      ebfffd07       BL     3c0 <_errata_CORTEXR4_57_>
    Fa0/:      e514301b       LDR    R3,[R4,#-27] ;0xffffff5.
    fa4:      e3130902       TST    R3,#32768     ;0x8000

请注意、R4一开始就被置位、GCC 不希望它在0xfa0处的 LDR 之前更改[EABI 表示 R4为被调用方保存]、但它确实如此、因此这会导致此时的数据中止。 由于 sys_startup.c 中的此代码代码,取指令会降低:

   /*SAFETYMCUSW 139 S MR:13.7 "硬件状态位读取检查"*/
   如果((SYS_EXCE异常 和安全装置复位)!=0U)则为其他

[具有讽刺意味的是、0xffffe5实际上是"正确的"、因为 R4返回0而不是停留在-1、它应该访问0xffe4、如果 R4仍然为-1。]  

针对这个问题的一个简单的权变措施是告诉 GCC 它的寄存器将被刷写:

/*用户代码开始(6)*/
   _ASM____volatile__(":"r0"、"r1"、"r2"、"r3"、"R4"、"R5"、"r6"、"r7"、"r8"、"r8"、"R9"、"r10"、"r11"、"r11"、"r11"、"R12"、"r14");
/*用户代码结束*/

请注意这对输出的作用:

00000f88 <_c_int00>:
    f88:      ebfffc5e       BL     108 <_coreInitRegisters_>
    f8c:      e3e04000       mvn    R4、#0
    f90:      ebfffc8a       BL     1C0 <_coreInitStackPointer_>
    f94:      ebfffca4       BL     22c <_coreEnableEventBusExport_>
    f98:      ebfffd0e       BL     3d8 <_errata_CORTEXR4_66_>
    f9c:      ebfffd07       BL     3c0 <_errata_CORTEXR4_57_>
    Fa0/:      e514301b       LDR    R3,[R4,#-27] ;0xffffff5.
    fa4:      e3130902       TST    R3,#32768     ;0x8000

MVN 现已在非 EABI 兼容函数调用后移动-其他函数调用充分符合 EABI、不会中断- LDR 成功。

对此可能有一种更巧妙的解决方案、但只要允许 GCC 按顺序分配内容、对不符合 EABI 的汇编函数的任何调用都可能会有问题。

这可能会在 HALCoGen 中修复。

仅供参考,在使用 CCS 和 TI 编译器进行编译时(HALCoGen 会发出 TI 代码),我们可以看到:

00003c44 <_c_int00>:
   3c44:      ebfffde3       BL     33d8 <_coreInitRegisters_>
   3c48:      ebfffe10       BL     3490 <_coreInitStackPointer_>
   3c4c:      ebfffe2a       BL     34fc <_coreEnableEventBusExport_>
   3c50:      ebfffe92       BL     36a0 <_errata_CORTEXR4_66_>
   3c54:      ebfffe8d       BL     3690 <_errata_CORTEXR4_57_>
   3c58:      e3e0401b       mvn    R4、#27
   3c5c:      e594c000       LDR    IP、[R4]
   3c60:      e31c0902       TST    IP,#32768     ;0x8000

虽然它在存储器中的饱和值比 GCC 输出高一些、但我们可以看到它不像 GCC 那样提早分配 R4、因此可以避免这一问题。

此致、


Patrick Herborn。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Patrick:

    请参阅 Chester.Gillon 在2020年12月5日发布的补丁。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    [引用 user="QJ Wang ">请参阅 Chester.Gillon 于2020年12月5日发布的补丁。我创建的补丁是 GCC 编译器、用于添加大端运行时库。

    而 Patrick 发现了一个需要在 HALCoGen 中解决的问题。

    我假设 Patrick 已经修补了 GCC 编译器、否则将无法链接大端序[BE32]格式 TMS570LS1225器件的可执行文件。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Hiya QJ、Chester!

    切斯特确实正确地指出、我遇到的问题是 HALCoGen 问题、与 GCC 本身无关。 HALCoGen 正在发射不符合 EABI 的汇编代码、这可能会损坏任何符合 EABI 的编译器-很幸运、TI 编译器未陷入此陷阱。 如前所述、应在 HALCoGen 中修复它。 从被调用方保存的 C 和 clobber 寄存器调用汇编是不正确的。 如前所述、asm 函数别无选择、只能对其进行 clober 处理(这就是它的全部目的)-因此、将复位矢量指向该代码、然后分支到_c_int00也许更正确。 asm ()权变措施显然能满足编译器的需要、因为它告诉编译器"我刚刚对您的寄存器进行了重写、因此您不能假设您所做的任何操作仍然有效"-尽管我只能确认它与 GCC 配合使用(其他编译器将具有其他机制)。 此外、如上所述、可能有一种更巧妙的方法(一些__attribute__或#pragma 指令编译器这是一个"断开"/"非 EABI 函数)。

    关于切斯特对 GCC“大端字节序”构建的假设,我得出以下结论(在很大程度上,基于切斯特在链接线程中的广泛贡献中的信息-一个优秀的资源):

    o 无需构建 binutils 的"大端字节序"版本-即使是我之前构建的版本、对于 bigarm 文件和-EB 标志来说也没有问题

    o GCC 本身不需要以"大端字节序"的方式构建-即使是我之前构建的版本、也可以生成 bigarm 文件并接受-mbig-endian 标志

    o GCC 编译中"需要"大端字节序的唯一部分是 libs、您可以使用 GCC 的./configure 中的 CFLAGS_for_target='-g -O2 -mbig-endian'轻松构建它们

    o 严格来说、没有必要为裸机工作使用大端字节序库。 实际上、我的 HALCoGen GCC 链接器脚本会尝试从 libc.a、libm.a、libgcc.a.中排除内容

    我还看到了一些其他的怪异现象:

    o 使用 GCC 作为前端进行链接会导致输出错误(它在 bigarm 文件中是小端字节序-转到图!)

    o 使用 LD 链接直接使用包含 bigarm 内容的 bigarm 文件创建预期结果

    o 使用 LD、可能是由于链接器脚本尝试排除 lib*。a、只要从 litlearm libs 中没有任何内容被拉入、我就能够在链接列表中包含 littlearm 文件(可以预期 LD 可能会对它产生影响、 但是、当您实际上没有从具有不同字节序的库中提取任何内容时、意识到字节序是没有意义的、这似乎足够聪明了)。

    接下来、我将尝试构建 GCC 10、看看它的行为-目前为止测试的是 GCC 9.2和9.3。

    此致、

    Patrick Herborn。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

     每次加电时、需要_coreInitRegisters_函数来初始化所有 CPU 用户寄存器一次、因为 ARM 不保证加电后用户寄存器的默认内容。 如果保持未初始化状态、则可能会根据两个 CPU 之间的寄存器内容不同而产生内核比较错误。

    您还可以根据自己的要求更改此函数。 例如、此函数已经在每个 CPU 模式间切换以初始化分组寄存器。 在这种情况下、您可以在该函数本身内集成针对每个 CPU 模式的堆栈指针初始化、而不是调用单独的函数来针对每个模式初始化 R13 (SP)寄存器(并为初始化节省一些时间)。 为此、您可以创建一个名为_initCoreRegs_的函数、并在加电时从 start-up.c 调用它、如下所示:

    /*用户代码开始(5)*/
    #if 0
    /*用户代码结束*/
    
    /*初始化内核寄存器以避免 CCM 错误*/
    _coreInitRegisters_();
    
    /*用户代码开始(6)*/
    /*用户代码结束*/
    
    /*初始化堆栈指针*/
    _coreInitStackPointer_();
    
    //用户代码开始(7)*/
    #endif
    
    _initCoreRegs_();
    /*用户代码结束*/
    

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    [引用 user="Patrick Herborn"]对此可能有更巧妙的解决方案、但只要允许 GCC 按顺序分配内容、对不符合 EABI 的汇编函数的任何调用都可能会有问题。我刚刚尝试将  returns_two 函数属性添加到 _coreInitRegisters_和 _coreInitStackpinter_的原型:

    /**@fn void _coreInitRegisters_(void)
    *@简要初始化核心寄存
    器*/
    void _coreInitRegisters_(void)__attribute__((returns_two));
    
    //@fn void _coreInitStackPointer_(void)
    *@简要初始化核心堆栈指针
    */
    void _eInitStack_itInters_(void)(void)_(void)_(void)_(void)_)
    

    为了测试为 TMS570LS1225创建的程序以及 使用 HALCoGen 04.07.01创建的 sys_core.h、即没有函数属性、并且使用 -O3 生成了有问题的代码:

    0000a8c8 <_c_int00>:
    *以确定 CPU 复位的原因。
    *
    
    /*检查上电复位条件*/
    /*SAFETYMCUSW 139 S MR:13.7 "硬件状态位读取检查"*/
    if ((SYS_EXception & powerON_RESET)!= 0U)
    a8c8: e3e04000 mvn R4、#0
    _coreInitRegisters_();
    a8cc: ebfff3d1 BL 7818. <_coreInitRegisters_>
    _coreInitStackPointer_();
    a8d0: ebfff413 BL 7924 <_coreInitStackPointer_>
    _coreEnableEventBusExport_();
    a8d4: ebfff433 BL 79a8. <_coreEnableEventBusExport_>
    _errata_CORTEXR4_66_(); 

    添加 returns_two 函数属性后、GCC 将对 R4的写入移动到_coreInitStackPointer_的调用之后:

    0000a8c8 <_c_int00>:
    {/*
    用户代码开始(5)*/*
    用户代码结束*/
    
    /*初始化内核寄存器以避免 CCM 错误*/
    _coreInitRegisters_();
    a8c8: ebfff3d2 BL 7818. <_coreInitRegisters_>
    
    /*用户代码开始(6)*/*
    用户代码结束*/
    
    /*初始化堆栈指针*/
    _coreInitStackPointer_();
    a8cc: 请 ebfff414 BL 7924 <_coreInitStackPointer_>
    *以确定 CPU 复位的原因。
    *
    
    /*检查上电复位条件*/
    /*SAFETYMCUSW 139 S MR:13.7 "硬件状态位读取检查"*/
    if ((SYS_EXception & powerON_RESET)!= 0U)
    a8d0: e3e04000 mvn R4、#0
    _coreEnableEventBusExport_(); 

    将函数属性添加到 sys_core.h 中的原型的问题是它们不在 用户代码块中、因此当 HALCoGen 重新生成代码时、更改会丢失。

    另一种方法是不修改 sys_core.h 中的函数原型、并在将其用于 _c_int00 ()之前在用户代码中添加具有函数属性的原型:

    void _c_int00 (void)
    {
    //用户代码开始(5)*/
    void _coreInitRegisters_(void)__attribute__((returns_two));
    void _coreInitStackPointer_(void)__attribute__((returns_two));
    //用户代码结束*
    
    /*初始化内核寄存器以避免 CCM 错误*/
    _coreInitRegisters_();
    
    /*用户代码开始(6)*/
    /*用户代码结束*/
    
    /*初始化堆栈指针*/
    _coreInitStackPointer_();
    

    GCC 7.2.1编译 器采用具有函数属性的第二个函数原型、而不是 sys_core.h 文件中没有函数属性的函数原型、并且在调用_coreInitStackPointer_()后仍将写入移动到 R4。 但是、让编译器两次看到具有不同函数属性的同一函数的原型可能会有问题、并且可能违反编码准则。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    很棒的酒店!

    我想我们都同意_coreInitRegisters_的功能、应该做的以及它需要做的原因。

    如果在如何开展这项工作方面存在一些分歧。 以您的示例为例、它似乎在移动、而不是缓解问题。

    自首次发布以来、我已经有更多的时间来考虑这一点、并且坦率地说、根本没有符合 AAPCS / EABI 的方法来做它必须做的事情。 引用 AAPCS:

    "子例程必须保留寄存器 R4-R8、R10、r11和 SP (以及将 R9指定为 v6的 PCS 变体中的 R9)的内容"

    因此、根据定义、不可能写入 AAPCS / EABI 函数、该函数执行_coreInitRegisters_必须执行的操作-它必须设置这些寄存器、因此它无法符合 AAPCS / EABI。

    应该注意的是、_coreInitStackPointer_也会中断 AAPCS / EABI、但这不会在这里表现为问题、因为_c_int00是_属性_((naked))和_属性_((naleturn))-因此编译器不像使用 stmfd R13! (在初始化堆栈指针之前、这会非常糟糕!) 但同样、任何符合 AAPCS/EBIA 标准的编译器都有权期望 SP 在整个函数调用中保持不变(很明显、_coreInitStackPointer_的目的是更改它们)。 因此、让复位矢量跳转到_coreInitRegisters_也许是明智之举、一旦所有非 AAPCS/EABI 兼容的启动代码均已运行且所有其他函数都可以符合 PACS/EABI、该矢量随后会流入或跳转到_c_int00。

    通过上面建议的 asm ()权变措施以外的一些编译器指令、也许仍然有可能告诉编译器一个特定的函数是"断开的"/不符合 AAPCS/EABI、也许这是一个比我建议的更巧妙的解决方案。 对于 GCC9.x、它至少在调用这些函数之前向 R4分配一个值、并且期望 R4在返回时具有相同的值-这会导致数据中止(可能未对齐访问)、因为 R4实际上已更改。

    [注意事项:为了确保两个内核具有相同的寄存器内容,我想可以先启动 SP,执行 stmfd R13! 后跟 ldmfd R13! 大概只有个内核实际存储到 RAM 中、因此在获取时、两个内核最终将具有相同的寄存器值-一个具有写入功能的内核中存在的寄存器值-当然、SP 被封装的事实仍然会破坏 AAPCS/EABI、 它不会破坏进程中的可执行文件]

    此致、

    Patrick Herborn。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    海雅切斯特!

    使用__attribute__(returns_two)的有趣主意,尽管正如您正确地说的那样,使用多个函数原型并不理想(但我的 asm ()解决方案也不理想)。 TI 可能会推出适用于 HALCoGen 04.07.02的解决方案... 尽管我仍然认为、在缓解该问题的同时、(大概)它不符合 AAPCS/EABI 标准-而通过_coreInitRegisters_和_coreInitStackPointer_发送复位矢量将是(即在从 ASM 切换到 C 之前、先清除不良内容)。

    此致、

    患者

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    [引用 user="Patrick Herborn">使用__attribute__((returns_two))的有趣创意,尽管正如您正确地说,使用多个函数原型并不理想(但我的 asm ()解决方案也不理想)。查看 HALCoGen 安装,似乎有源文件,这些源文件是 HALCoGen 生成的源文件模板, 通过标记有条件地排除文件的段或设置参数值。

    其中、c:/ti/Hercules/HALCoGen/v04.07.01/drivers/TMS570LS3137ZWT/SYSTEM570v000/sys_core.h 显示在 用于 TMS570LS1225的 sys_core.h 头文件的模板中。 如果您准备修改 HALCoGen 安装、大概可以修改"模板"文件。 例如、向  _coreInitRegisters_和 _coreInitStackPointer_的原型添加__attribute__((returns_two))。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    海雅切斯特!

    在 HALCoGen 安装中确实有许多模板、并且在特定于器件的配置中似乎详细介绍了实际部署的模板、因此这些文件有多个版本。 有趣的是、我的已发出代码中有一些勘误调用、而 TMS570LS3137版本中缺少这些调用-也许我有一个之前的版本、并且没有正确清除那些允许前一个版本持续存在的错误调用(如果不需要实际更改、可能会发生这种情况)。 我将尝试清除并重新发射、以查看是否将其拖放至相应的[特定于器件的配置确实引用了3137版本]。

    至于修改 HALCoGen 源、我想有三种方法来修改它- 1)将您的补丁应用于函数声明、2)应用我的 asm ()补丁、或者3)更改复位矢量、以便在 asm 跳转到 C 代码之前跳转到 asm。 重新阅读 GCC 文档表明1)和2)对 GCC 具有类似的影响、因为它们都告诉 GCC "不要假定您通常所假设的事情"。

    returns_two 属性似乎存在以实现 setjmp()和 vfork()(或以"意外"方式运行的其他函数)。 这可能是一种比我的 asm ()修补程序更巧妙的方法,告诉 GCC“这个函数做怪异的事情”[尽管后者可以进入用户代码部分,而不需要 returns_double 的用户代码实现所需的双声明]。 这两种方法都不能直接解决_coreInitRegisters_()不符合 EABI 的问题-但通过相同的令牌、setjmp()不能是([编辑: 除非 setjmp()保存所有这些寄存器,并且 longjmp()恢复它们],因为无法预测其他寄存器在该寄存器和 longjmp()之间发生的情况,而 vfork()可能会以完整的寄存器返回)。 GCC 文档有点令人困惑、其中他们说 regs 在进入 returns_twice 函数时已死、而不是在返回时( 至少对我而言)看起来更清晰。

    我想最终归结为程序员是否希望在 CPU 到达复位矢量和使寄存器保持平坦的代码之间插入其他合法内容、以确保两个内核具有相同的状态。 我不能想象在这之前需要做的任何事情[当然、其他人可能会这么做]、因此我的直觉是、将复位矢量设置为_coreInitRegisters_、并且让它也初始化 SP 可能会很好。 这些任务永远不能符合 EABI、而此后的几乎所有任务都可以[禁止任何 setjmp()/longjmp()]。

    此致、

    Patrick Herborn。