使用gcc在裸金属ARM上的RAM和ROM部分之间进行c-long_calls



我正在使用GCC 4.3进行一个ARM7TDMI项目,我很难告诉编译器在某些情况下使用长调用,但在其他情况下则不能。

构建过程运行arm-eabi-gcc为每个.c源文件生成可重定位的ELF对象文件(最相关的CFLAGS包括-Os -ffunction-sections -fdata-sections -mthumb -mthumb-interwork),然后将它们全部链接到ELF可执行文件(最重要的LDFLAGS包括-Wl,--gc-sections -Wl,-static -Wl,-n -nostdlib和自定义链接器脚本)。然后,使用arm-eabi-objcopy -O binary将ELF文件转换为原始可执行文件,并在启动时由自定义引导加载程序将其从ROM复制到RAM(一个同时包含代码和数据的SRAM)。因此,所有的东西都从RAM执行,.rodata存在于RAM中,并且一切都很快进行,完全忽略了引导后的ROM。

我现在正试图改变这一点,以便某些选定的RO数据和选定函数的文本只能存在于ROM中,并在运行时根据需要进行访问。我已经修改了链接器脚本,以了解两个新的部分".flashdata"".flashtext",这两个部分都应该放在ROM中的一个固定地址。我还在整个C代码中适当地添加了__attribute__((__section__(".flashdata")))__attribute__((__section__(".flashtext"),__long_call__)),并且我重新调整了构建过程,以便旧的objcopy现在添加-R .flashdata -R .flashtext,我用-j为这些部分中的每一个做第二个objcopy,然后我将这两个输出文件组合起来,这样引导加载程序就可以做正确的事情,并且ROM部分出现在预期的内存映射位置。

这一切都很好——我可以打印标记到.flashdata部分的f字符串,并且我可以从RAM用完的代码中调用.flashtext函数(由于__section__(".flashtext")属性旁边的__long_call__属性,它知道要使用长调用)。基于ROM的函数可以愉快地短调用其他基于ROM的功能,并且可以返回到基于RAM的调用方。

问题出现在试图从基于ROM的函数调用到基于RAM的函数时,这也必须是一个长调用。我不想在任何地方都使用长呼叫,所以我不希望在我的CFLAGS中使用-mlong_calls。如果我把ROM中的所有函数都分组到一个rom.c中,我就可以用-mlong-calls构建一个文件,一切都正常。然而,我强烈希望避免这种情况,并保持函数按目的分组,只需在此处和那里标记一些适当的函数即可从ROM运行。

顺便说一句,根据gcc 3.4,这还不够。使用-mlong-calls让编译器思考了正确的事情,但它无法坚持到底,因为它只愿意与助手_call_via_rX一起执行跳远。。。它们都存在于RAM中,并且只能通过长调用来访问。这在gcc 4.0中的链接器中得到了修复,但没有向后移植到3.x树中的任何内容。

因此,由于我使用的是gcc 4.3,我现在可以调用回RAM,这真是太棒了。如果我能以某种方式在基于ROM的函数中标记代码,迫使它使用长调用,那就更好了。有一个#pragma long_calls,但它只影响声明,所以我可以使用它来代替__attribute__((__long_call__))。遗憾的是,它并没有神奇地强制编译器对它生效时遇到的所有函数调用使用长调用。

从组织上讲,将所有运行缓慢的代码分组到一个文件中,断章取义,并与一般类别中的其他代码分开,这根本不是正确的做法。请告诉我还有一个选择我还没有考虑。为什么-ffunction部分或代码位于不同部分(.text.flashtext)这一事实不能自动解决我的问题?

顺便说一句,当发现编译器使用了一个短调用,而该调用没有给它留下足够的空间来管理重新定位时,链接器的错误是:relocation truncated to fit: R_ARM_THM_CALL against symbol foo'在CFLAGS中的objs/foo.o (and the section.text.foo is used instead of.text because of-function sections `中定义)。

我怀疑没有办法自动执行您想要的操作。我确实有一个建议,尽管这不是你想要的。

像这样声明RAM函数(比如在RAM_funcs.h中):

int foo() RAM_CALL;

然后把你所有的ROM功能放进它们自己的文件中,并像这样启动每个文件:

#define RAM_CALL __attribute__((__long_call__))
#include "ram_funcs.h"

其他文件将使用:

#define RAM_CALL
#include "ram_funcs.h"

这并不比使用-mlong-calls好多少,但至少它将逻辑放在函数本身附近,而不是放在某些编译选项中。例如,可以更容易地在每个.c文件中放入一些函数。

这个问题可能在gcc 4.3.3及更高版本中得到了解决,但我一直在使用4.3.2。我创建了一个不会执行的pet示例,但演示了不同部分的使用以及由此产生的链接错误。它无法使用Codesourcery的arm-2008q3进行构建,但它确实使用arm-2009q1及更高版本进行了构建。我需要更多的时间来更新整个项目以使用更新的gcc版本,所以我还不能肯定地说这解决了我的问题,但我强烈怀疑它确实解决了。

顺便说一句,我有另一种变通方法,可以将所有基于ROM的函数分组为-mthumb-calls构建的ROM.c:通过函数指针调用所有函数。在这种变通方法的情况下,治愈比疾病更糟糕:

((void(*)(void*, void*, int))&memcpy+1)(&dest, &src, len);

您需要+1,这样优化器就不会智胜您,但在我的情况下这不是问题,因为bx指向奇数地址表示拇指模式,而我所有的代码都是拇指模式。然而,我不认为有一种方法可以制作一个通用宏来包装所有这样的函数调用,因为每个指针都需要显式转换以匹配返回类型和参数列表——实际上,你最终会显式地重新声明你想要调用的每一个函数,甚至是你没有提供的库函数。

为了防止其他人像我一样遇到这个问题,下面是我提出的解决方案。

正如Eric Angell所说,理想情况下,您可以指定要通过长调用调用函数。因为在使用GCC的C/C++中无法直接做到这一点,我转向汇编程序,编写了一些宏来拦截GCC生成的bl和blx程序集,并用长调用替换它们。

//longCall.h
#pragma once
#define INLINE inline __attribute__((always_inline))
template<typename T1, typename T2>
struct IsType {
    constexpr operator bool() { return false; }
};
template<typename T>
struct IsType<T, T> {
    constexpr operator bool() { return true; }
};
INLINE void EnableLongCallBranches() {
    asm volatile(R"(
        interceptBranchAndConditions bl
        interceptBranchAndConditions blx
    )");
}
INLINE void DisableLongCallBranches() {
    asm volatile(R"(
        disableInterceptBranchAndConditions bl
        disableInterceptBranchAndConditions blx
    )");
}
template<auto func, typename... ArgsT>
INLINE auto LongCall(ArgsT... args) {
    EnableLongCallBranches();
    //Check if function call returns void
    if constexpr(IsType<decltype(func(args...)), void>()) {
    
        //just evalute function
        func(args...);
        DisableLongCallBranches();
    
    } else {
        //return result of function
        //Note: cannot simply use 'return func(args...)' or else compiler will treat 
        //      'DisableLongCallBranches()' as dead code and remove it 
        auto val = func(args...);
        DisableLongCallBranches();
        return val;
    }
}
//Static assembly macros to intercept and replace relative branches with long jumps
asm(R"(
    
    @ macro for intercepting a branch call and replacing them with calls to longCallIntercept
    .macro interceptBranch instruction, condition
        .macro instructioncondition label
            longCallIntercept instruction, condition, label
        .endm
    .endm
    
    @ macro for intercepting a branch call and all of its conditnal calls with longCallIntercept
    .macro interceptBranchAndConditions instruction
        interceptBranch instruction, "eq" @ Equal. Z==1
        interceptBranch instruction, "ne" @ Not equal. Z==0
        interceptBranch instruction, "cs" @ Unsigned higher or same (or carry set). C==1
        interceptBranch instruction, "hs" @ Unsigned higher or same (or carry set). C==1
        interceptBranch instruction, "cc" @ Unsigned lower (or carry clear). C==0
        interceptBranch instruction, "lo" @ Unsigned lower (or carry clear). C==0
        interceptBranch instruction, "mi" @ Negative. The mnemonic stands for "minus". N==1
        interceptBranch instruction, "pl" @ Positive or zero. The mnemonic stands for "plus". N==0
        interceptBranch instruction, "vs" @ Signed overflow. The mnemonic stands for "V set". V==1
        interceptBranch instruction, "vc" @ No signed overflow. The mnemonic stands for "V clear". V==0
        interceptBranch instruction, "hi" @ Unsigned higher. (C==1) && (Z==0)
        interceptBranch instruction, "ls" @ Unsigned lower or same. (C==0) || (Z==1)
        interceptBranch instruction, "ge" @ Signed greater than or equal. N==V
        interceptBranch instruction, "lt" @ Signed less than. N!=V
        interceptBranch instruction, "gt" @ Signed greater than. (Z==0) && (N==V)
        interceptBranch instruction, "le" @ Signed less than or equal. (Z==1) || (N!=V)
        interceptBranch instruction, "al" @ Always executed.
        interceptBranch instruction, ""   @ Always executed.
    .endm
    
    @ macro for deleting all branch call and condition call interception macros
    .macro disableInterceptBranchAndConditions instruction
        .purgem instruction()eq @ Equal.  Z==1
        .purgem instruction()ne @ Not equal.  Z==0
        .purgem instruction()cs @ Unsigned higher or same (or carry set). C==1
        .purgem instruction()hs @ Unsigned higher or same (or carry set). C==1
        .purgem instruction()cc @ Unsigned lower (or carry clear). C==0
        .purgem instruction()lo @ Unsigned lower (or carry clear). C==0
        .purgem instruction()mi @ Negative. The mnemonic stands for "minus".  N==1
        .purgem instruction()pl @ Positive or zero. The mnemonic stands for "plus". N==0
        .purgem instruction()vs @ Signed overflow. The mnemonic stands for "V set". V==1
        .purgem instruction()vc @ No signed overflow. The mnemonic stands for "V clear".  V==0
        .purgem instruction()hi @ Unsigned higher. (C==1) && (Z==0)
        .purgem instruction()ls @ Unsigned lower or same. (C==0) || (Z==1)
        .purgem instruction()ge @ Signed greater than or equal. N==V
        .purgem instruction()lt @ Signed less than. N!=V
        .purgem instruction()gt @ Signed greater than. (Z==0) && (N==V)
        .purgem instruction()le @ Signed less than or equal.  (Z==1) || (N!=V)
        .purgem instruction()al @ Always executed.
        .purgem instruction      @ Always executed.
    .endm
    
    @ macro for replacing relative jumps with long jumps
    .macro longCallIntercept instruction, condition, label
        
        @ check if label is register
        .set _longCallIntercept_LabelIsReg, 0
        .irp register r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, ip, lr, pc
            .ifeqs "label", "register"
                .set _longCallIntercept_LabelIsReg, 1
            .endif
        .endr
        @ delete instruction macro so 'instruction' is treated as opcode
        .purgem blxcondition
    
        .if _longCallIntercept_LabelIsReg
    
            .warning "Ignoring: [instruction label] long call"
    
            @ keep existing branch to register instruction
            instructioncondition label
    
        .else
            .print "Replacing: [instruction label] with long call"
        
            @ invoke long branch
            @ Note: could use 'ldr ip, =label' to favor size over speed
            movw ip, #:lower16:label
            movt ip, #:upper16:label
            blxcondition ip
    
        .endif
 
        @ Redefine instruction macro
        interceptBranch blx, condition
    .endm
)");

现在,在代码中的任何位置,都可以明确指定要长的调用。

//example.cpp
#include "longCall.h"
//Note: Only marked noinline to prevent optimize from evaluating
//      function at compiletime
__attribute__((noinline)) int foo(int x) { 
    return x;
}
int main() {
    
    int v1 = LongCall<foo>(1);
    int v2 = foo(2);   
    return v1 + v2;
}

输出程序集:

foo(int):
    bx      lr
main:
    push    {r4, lr}
    
    @ long call to foo(1) 
    movs    r0, #1
    movw    ip, #445    ; 0x1bd
    movt    ip, #1
    blx     ip
    
    mov     r4, r0
    
    @ short call to foo(2)
    movs    r0, #2
    bl      101bc       ; <foo(int)>
    
    add     r0, r4
    pop     {r4, pc}

下面是上面例子的编译器资源管理器。

最新更新