直接跳转到另一个C++函数

7
我将一个小型的学术操作系统从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)时,堆栈和CSAs保持不变。

现在我需要在ARM Cortex(或者更确切地说,在arm-none-linux-gnueabi-g++上)实现相同的行为,即生成B(又名BRANCH)指令,而不是BLX(又名BRANCH with link and exchange)。但是在arm-g++中没有interrupt_handler属性,我也找不到任何等效的属性。

因此,我尝试采用asm volatile并直接编写汇编代码:

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中了(我研究过一些资料,但没有发现任何提示)。

a1、a2、a3是指针吗?尝试将它们转换为(void*) - osgx
ARM中断处理程序的属性是interrupt。请参阅http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html。 - Mike Seymour
你可以像这样将变量声明为特定寄存器:register int reg0 asm("r0") = a1;。http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html - Mike Seymour
@Mike: (1) 我会尝试一下;(2) 要么我没理解你的意思--要么你在问题描述中忽略了它... - orithena
我曾认为错误可能是由于实例化模板的方式导致的。但是不是这样的 - 请看我的答案。 - Mike Seymour
显示剩余3条评论
2个回答

1
链接错误是由于尝试使用分支指令跳转到指针而引起的。这会生成类似于b r6 的代码,无法链接,因为r6不是符号。将分支指令更改为mov pc,%0,您应该能够正确跳转。
正如我在评论中提到的那样,ARM中断处理程序是使用interrupt属性声明的,但正如您发现的那样,这不会影响它们被调用的方式。我猜这是一个特定于平台的技巧,恰好在TriCore上做了正确的事情。
您可以尝试使用GCC的扩展语法,在特定寄存器中声明变量,例如register int reg0 asm("r0") = a1;,而不是易失性的mov指令。这可能允许编译器生成更好的代码。

0

好的,我现在明白了出了什么问题。

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

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

关于链接器错误,提示未定义的引用r6...嗯,我应该更仔细地阅读指令集文档。 B 是无条件跳转到一个立即地址的指令,而BX是期望在寄存器中传递跳转地址的指令。我被手册中的指令列表所迷惑,其中BX被简要描述为“带交换的分支”。我不想交换任何东西,我只想进行简单的跳转,所以我没有继续阅读。
因此,在asm volatile代码中将B替换为BX后,代码编译通过了。但是,正如上面指出的那样,整个概念不能按预期工作。也许其他人可以找到这段代码的用例,但我现在必须采用经典的函数调用方法...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接