直接跳转到另一个C++功能

发布于 2024-08-26 23:46:25 字数 1840 浏览 7 评论 0原文

我正在将一个小型学术操作系统从 TriCore 移植到 ARM Cortex(Thumb-2 指令集)。为了使调度程序正常工作,我有时需要直接跳转到另一个函数,而不修改堆栈或链接寄存器。

在 TriCore(或者更确切地说,在 tricore-g++)上,此包装器模板(对于任何三参数函数)有效:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    typedef void (* __attribute__((interrupt_handler)) Jump3)( A1, A2, A3);
    ( (Jump3)func )( a1, a2, a3 );
}

//example for using the template:
JUMP3( superDispatch, this, me, next );

这将生成汇编器指令 J (又名 JUMP)而不是 CALL,在跳转到(否则正常)C++ 函数 superDispatch(SchedulerImplementation* obj, Task::Id from, Task::Id to) 时,保持堆栈和 CSA 不变。

现在我需要在 ARM Cortex(或者更确切地说,对于 arm-none-linux-gnueabi-g++)上有一个等效的行为,即生成一个 B (又名 BRANCH)指令而不是 BLX(又名带有链接和交换的分支)。但是arm-g++没有interrupt_handler属性,我找不到任何等效的属性。

因此,我尝试求助于 asm volatile 并直接编写 asm 代码:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    asm volatile (
                  "mov.w r0, %1;"
                  "mov.w r1, %2;"
                  "mov.w r2, %3;"
                  "b %0;"
                            :
                            : "r"(func), "r"(a1), "r"(a2), "r"(a3)
                            : "r0", "r1", "r2"
                  );
}

​​到目前为止,至少在我的理论中,效果很好。 Thumb-2 需要在寄存器中传递函数参数,即本例中的 r0..r2,因此它应该可以工作。

但随后链接器在

undefined reference to `r6'

asm 语句的右括号处消失......我不知道该怎么做。好吧,我不是 C++ 专家,而且 asm 语法也不是很简单...所以有人给我提示吗?一种方法是提示arm-g++ 的正确__attribute__,而提示修复asm 代码则是另一种方法。另一种方法可能是告诉编译器,当输入 asm 语句时,a1..a3 应该已经在寄存器 r0..r2 中(我研究了一个位,但没有发现任何提示)。

I'm porting a small academic OS from TriCore to ARM Cortex (Thumb-2 instruction set). For the scheduler to work, I sometimes need to JUMP directly to another function without modifying the stack nor the link register.

On TriCore (or, rather, on tricore-g++), this wrapper template (for any three-argument-function) works:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    typedef void (* __attribute__((interrupt_handler)) Jump3)( A1, A2, A3);
    ( (Jump3)func )( a1, a2, a3 );
}

//example for using the template:
JUMP3( superDispatch, this, me, next );

This would generate the assembler instruction J (a.k.a. JUMP) instead of CALL, leaving the stack and CSAs unchanged when jumping to the (otherwise normal) C++ function superDispatch(SchedulerImplementation* obj, Task::Id from, Task::Id to).

Now I need an equivalent behaviour on ARM Cortex (or, rather, for arm-none-linux-gnueabi-g++), i.e. generate a B (a.k.a. BRANCH) instruction instead of BLX (a.k.a. BRANCH with link and exchange). But there is no interrupt_handler attribute for arm-g++ and I could not find any equivalent attribute.

So I tried to resort to asm volatile and writing the asm code directly:

template< class A1, class A2, class A3 > 
inline void __attribute__((always_inline)) 
JUMP3( void (*func)( A1, A2, A3), A1 a1, A2 a2, A3 a3 ) {
    asm volatile (
                  "mov.w r0, %1;"
                  "mov.w r1, %2;"
                  "mov.w r2, %3;"
                  "b %0;"
                            :
                            : "r"(func), "r"(a1), "r"(a2), "r"(a3)
                            : "r0", "r1", "r2"
                  );
}

So far, so good, in my theory, at least. Thumb-2 requires function arguments to be passed in the registers, i.e. r0..r2 in this case, so it should work.

But then the linker dies with

undefined reference to `r6'

on the closing bracket of the asm statement ... and I don't know what to make of it. OK, I'm not the expert in C++, and the asm syntax is not very straightforward... so has anybody got a hint for me? A hint to the correct __attribute__ for arm-g++ would be one way, a hint to fix the asm code would be another. Another way maybe would be to tell the compiler that a1..a3 should already be in the registers r0..r2 when the asm statement is entered (I looked into that a bit, but did not find any hint).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

月依秋水 2024-09-02 23:46:25

链接错误是由于尝试使用分支指令跳转到指针而引起的。这会生成类似 b r6 的代码,该代码无法链接,因为 r6 不是符号。将分支指令更改为 mov pc,%0,您应该得到正确的跳转。

正如我在评论中提到的,ARM 中断处理程序是使用 interrupt 属性声明的,但正如您发现的那样,这不会影响它们的调用方式。我想这是一个特定于平台的技巧,恰好在 TriCore 上做了正确的事情。

您可以尝试使用 GCC 的扩展语法 register int reg0 asm("r0") = a1; 而不是易失性 mov 指令在特定寄存器中声明变量。这可能允许编译器生成更好的代码。

The link error is caused by trying to use the branch instruction to jump to a pointer. This generates code like b r6, which fails to link because r6 isn't a symbol. Change the branch instruction to mov pc,%0, and you should get the correct jump.

As I mentioned in the comments, ARM interrupt handlers are declared with an interrupt attribute, but as you found this doesn't effect how they're called. I guess that was a platform-specific trick that happened to do the right thing on TriCore.

You could try declaring variables in specific registers using GCC's extended syntax, register int reg0 asm("r0") = a1; rather than volatile mov instructions. This might allow the compiler to generate better code.

樱娆 2024-09-02 23:46:25

好吧,我现在知道出了什么问题了。

直接跳转到另一个函数的整个概念在 ARM Cortex 上是没有意义的,因为每次调用另一个函数时,TriCore 使用上下文保存区域 (CSA) 来保存整个 CPU 上下文。将其视为第二个独立堆栈,该堆栈随着每次 CALL 增长并随着每次 RET 收缩。每个CSA块都有恒定的大小。

另一方面,ARM Cortex 使用一个简单的标准堆栈(好吧,它知道系统堆栈和线程堆栈,但这在这里并不重要)——而 GCC 只是保存每个函数所需的内容,因此每个帧都有一个不同的尺寸。因此,简单地跳转到另一个函数是不可能的,因为一旦跳转到的函数开始保存它使用的非易失性寄存器,堆栈就会损坏。

关于对 r6 的未定义引用的链接器错误......好吧,我应该更仔细地阅读指令集文档。 B 是到立即 地址的无条件分支,BX 是期望寄存器中分支地址的指令。我被手册中的指令列表愚弄了,其中 BX 被简短地描述为“带有交换的分支”。我不想交换任何东西,我想要一个简单的跳转,所以我没有继续阅读。

因此,在 asm volatile 代码中将 BBX 交换后,代码就编译好了。但是,正如上面指出的,整个概念无法按预期发挥作用。也许其他人可以找到该代码的用例,我现在必须求助于经典函数调用......

Well, I now figured out what went wrong.

The whole concept of JUMPing directly to another function is moot on ARM Cortex, because TriCore uses a Context Save Area (CSA) to save the whole CPU context everytime you call another function. Think of it as a second, independent stack that grows with each CALL and shrinks with each RET. And each CSA block has constant size.

ARM Cortex, on the other hand, uses a simple standard stack (ok, it knows about a system stack and a thread stack, but that's unimportant here) -- and GCC just saves what it needs for each function, so each frame has a different size. Simply jumping to another function therefore is out of the question, because the stack will be corrupt as soon as the jumped-to function starts saving the non-volatile registers it uses.

And about the linker error with the undefined reference to r6 ... well, I should have read the instruction set documentation more carefully. B is an unconditional branch to an immediate address, BX is the instruction that expects the branch address in a register. I was fooled by the instruction list in the manual where BX was shortly described as "Branch with exchange". I did not want to exchange anything, I wanted a simple jump, so I did not read further.

So, after exchanging B with BX in the asm volatile code, the code compiled. But, as pointed out above, the whole concept cannot work as expected. Maybe someone else can find a use case for that code, I have to resort to classic function calling now ...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文