我正在使用GCC 4.3开发一个ARM7TDMI项目,我有一些困难,告诉编译器在某些情况下使用长调用而不是其他情况。
构建过程运行arm-eabi-gcc
为每个.c源文件生成可重定位的ELF目标文件(最相关的CFLAGS包括-Os -ffunction-sections -fdata-sections -mthumb -mthumb-interwork
),然后将它们全部链接到ELF可执行文件(最相关的LDFLAGS包括{ {1}}和自定义链接描述文件。然后将该ELF文件转换为具有-Wl,--gc-sections -Wl,-static -Wl,-n -nostdlib
的原始可执行文件,并且自定义引导加载程序在启动时将其从ROM复制到RAM(具有代码和数据的单个SRAM)。所以一切都从RAM执行,arm-eabi-objcopy -O binary
存在于RAM中,一切都很快进行,完全忽略了ROM后的ROM。
我现在正在尝试更改它,以便某些选定的RO数据和选择函数的文本只能存在于ROM中,并在运行时根据需要进行访问。我已经修改了链接描述文件,以了解两个新的.rodata
和".flashdata"
部分,这两部分应该放在ROM中的固定地址。我还在适当的时候在整个C代码中添加了".flashtext"
和__attribute__((__section__(".flashdata")))
,并且我重新调整了构建过程,以便旧的objcopy现在添加__attribute__((__section__(".flashtext"),__long_call__))
,然后我再做第二次objcopy对于每个部分使用-R .flashdata -R .flashtext
,然后我组合两个输出文件,以便引导加载程序可以做正确的事情,ROM部分出现在预期的内存映射位置。
这一切都运行正常 - 我可以将printf字符串标记到-j
部分,我可以从运行RAM的代码中调用.flashdata
函数(因为它知道使用长调用.flashtext
属性旁边的__long_call__
属性。这个基于ROM的功能可以很快地调用其他基于ROM的函数,并且它可以返回到基于RAM的调用者。
问题在于尝试从基于ROM的函数调用基于RAM的函数,这也必须是长调用。我不想在任何地方使用长呼叫,所以我不想在我的CFLAGS中使用-mlong_calls。如果我将ROM中的所有函数分组到一个__section__(".flashtext")
中,我可以用rom.c
构建那个文件,一切正常。但是,我强烈希望避免这种情况,并保持功能通常按目的分组,只需在适当的位置标记一些以便从ROM运行。
顺便说一句,在gcc 3.4下这还不够。使用-mlong-calls
让编译器思考正确的事情,但它无法完成,因为它只愿意用它的助手-mlong-calls
执行长跳转...它们都存在于RAM中并且只能是通过长途电话访问。 This was fixed in the linker in gcc 4.0, but not backported to anything in the 3.x tree
因为我正在使用gcc 4.3,所以我现在可以回调RAM了。如果我能以某种方式标记基于ROM的函数中的代码以强制它使用长调用,那将更好。有一个#pragma long_calls
,但它只影响声明,因此我可以使用它而不是_call_via_rX
。令人遗憾的是,它并没有神奇地强制编译器对它生效时遇到的所有函数调用使用长调用。
在组织上,将所有运行缓慢的代码分组到单个文件中,脱离上下文并与其常规类别中的其他代码分开是不正确的。请告诉我有一个我尚未考虑过的选项。为什么不是-ffunction-sections或只是代码在不同的部分(__attribute__((__long_call__))
与.text
)自动解决我的问题?
顺便说一句,当链接器发现编译器使用了一个没有足够空间来管理重定位的短调用时,链接器的错误是:.flashtext
foo'在.text中定义。 objs / foo.o中的foo部分relocation truncated to fit: R_ARM_THM_CALL against symbol
。text.foo (and the section
。文本is used instead of
- CFLAGS中的ffunction-sections`。
答案 0 :(得分:3)
我怀疑没有办法自动做你想做的事。我确实有一个建议,虽然这不是你想要的。
声明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文件中放置一些函数会更容易。
答案 1 :(得分:3)
似乎问题可能已在gcc 4.3.3及更高版本中修复,但我一直在使用4.3.2。我创建了一个不会执行的宠物示例,但演示了使用不同的部分以及由此产生的链接错误。它无法使用Codesourcery arm-2008q3进行构建,但它确实使用arm-2009q1及更高版本构建。我需要更多时间来更新整个项目以使用更新的gcc版本,所以我还不能明确地说这可以解决我的问题,但我强烈怀疑它确实存在。
顺便说一下,我有另一个解决方法,作为将所有基于ROM的函数分组到-mthumb-calls
构建的rom.c的替代方法:通过函数指针调用所有内容。在这种解决方法的情况下,治愈比疾病更糟糕:
((void(*)(void*, void*, int))&memcpy+1)(&dest, &src, len);
你需要+1
以便优化器不会超越你,但在我的情况下这不是问题,因为bx
- 奇怪的地址表示拇指模式,以及我的所有代码是拇指。但是,我不相信有一种方法可以使一个通用的宏来包装所有这样的函数调用,因为每个指针都需要显式地转换为匹配返回类型和参数列表 - 你有效地最终明确地重新声明每个函数你想打电话,甚至是你没有提供的图书馆功能。
答案 2 :(得分:0)
以防万一其他人像我一样偶然发现这个问题,这是我想出的解决方案。
就像 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 \instruction\condition 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 blx\condition
.if _longCallIntercept_LabelIsReg
.warning "Ignoring: [\instruction \label] long call"
@ keep existing branch to register instruction
\instruction\condition \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
blx\condition 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;
}
Here's 上面例子的编译器资源管理器。