2015-10-16 103 views
0

是否可以使这个混合的C++/asm函数符合标准? 功能ePendSV()必须具有这样的布局:从裸露的asm函数访问C++非POD类数据

ePendSV: //function entry point 
    mrs r0,PSP 
    stmdb r0!,{r4-r11,lr} 
// compiler can generate any code here doing these things: 
    readyTcbQueue.pTcb.runTcb->psp = r0; 
    readyTcbQueue.pTcb.runTcb=readyTcbQueue.pTcb.readyTcb; 
    r0 = readyTcbQueue.pTcb.readyTcb->psp; 
// work with r0 in assembly 
    ldmia r0!,{r4-r11,lr} 
    msr PSP,r0 
    bx lr 

readyTcbQueue.pTcb是只有两个指针BragOsTcb对象一个简单的结构对象;

struct{ 
    BragOsTcb *runTcb; 
    BragOsTcb *readyTcb; 
}; 

BragOsTcb非POD类,但如果没有虚函数和虚拟继承,看起来像这样:

class BragOsTcb : public TcbCdllq, public TimerTcbCdllq, public BragOsObject{ 
public: 
    BragOsTcb(); 
.... 
private: 
..... 
public: 
    unsigned long psp; 
.... 
}; 

TcbCdllq,TimerTcbCdllq,BragOsObject也简单的类具有相似的布局和无虚函数。但他们也是非POD。

我已经使这段代码适用于gcc和clang,但它是一个非标准的黑客,可能无法正常工作。

__attribute__((naked)) void ePendSV(){ 
    asm volatile("\ 
    mrs r0,PSP \n\ 
    stmdb r0!,{r4-r11,lr} \n\ 
\n\ 
    ldr r1,=%0 \n\ 
    ldmia r1,{r2,r3} // r2=runTcb, r3=readyTcb \n\ 
    str r0,[r2,%1] // save psp \n\ 
    str r3,[r1,#0] // runTcb=readyTcb \n\ 
    ldr r0,[r3,%1] // readyTcb->psp \n\ 
\n\ 
    // restore LR(EXC_RETURN),R11-R4 from new PSP, set new PSP, return \n\ 
    ldmia r0!,{r4-r11,lr} \n\ 
    msr PSP,r0 \n\ 
    bx lr \n\ 
    " : 
     : "i"(&readyTcbQueue.pTcb),"i"(&(((BragOsTcb*)0)->psp)) 
     :); 
    // it'is an offsetof hack which might not work 
} 

谢谢!

+0

这是很难有理由认为PSP的ofsset将在运行时更改。你为什么担心?它是一个图书馆,将用于你使用的不同cmpiler吗? –

+0

是的,这是一个库,它可以在不同的编译器上使用。 psp的偏移在运行时不会改变。但它可以从编译器更改为编译器,或者可以被某些编译器视为错误。我用另外一个以上的asm关键字实现了这个功能。那个函数被clang-3.7抛弃了,并且出现了错误信息,但是在clang-3.5和gcc-4.8上工作。我可以做运行时检查以确保psp的偏移量是正确的:if((long)(&readyTcbQueue.pTcb-> psp) \t \t!=((long)readyTcbQueue.pTcb +(long)(&(((BragOsTcb * )0) - > psp)))){ \t \t \t unrecoverableError(“....”); \t}' – brag

+0

ePendSV是否会从您的图书馆外被调用?如果是这样,它本身就是一个问题,因为裸体属性是编译器特定的。但是,如果它是一个内部函数,那么你可以添加一个简短的asm子句,将你的地址加载到所需的寄存器中,然后调用函数 –

回答

0

所以你想要的是从一个裸函数访问prag成员的BragOsTcb,它可以没有参数,并且更多的是作为硬件中断处理程序调用的,所以你不能添加任何其他代码来加载地址为你。 BragOsTcb不是POD,所以你担心从不同编译器开始的psp成员会有所不同。

我建议以下方案:


struct BragOsTcbWrapper 
{ 
    BragOsTcb* this_; 
    unsigned long psp; 
}; 

struct{ 
    BragOsTcbWrapper *runTcb; 
    BragOsTcbWrapper *readyTcb; 
}; 

class BragOsTcb : public TcbCdllq, public TimerTcbCdllq, public BragOsObject{ 
public: 
    BragOsTcb() 
    : pspHolder({this,0}) 
    { 

    } 
.... 
private: 
..... 
public: 
    BragOsTcbWrapper pspHolder; 
.... 
}; 

现在的asemblly你会做什么:


__attribute__((naked)) void ePendSV(){ 
    asm volatile("\ 
    mrs r0,PSP \n\ 
    stmdb r0!,{r4-r11,lr} \n\ 
\n\ 
    ldr r1,=%0 \n\ 
    ldmia r1,{r2,r3} // r2=runTcb, r3=readyTcb \n\ 
    str r0,[r2,%1] // save psp \n\ 
    str r3,[r1,#0] // runTcb=readyTcb \n\ 
    ldr r0,[r3,%1] // readyTcb->psp \n\ 
\n\ 
    // restore LR(EXC_RETURN),R11-R4 from new PSP, set new PSP, return \n\ 
    ldmia r0!,{r4-r11,lr} \n\ 
    msr PSP,r0 \n\ 
    bx lr \n\ 
    " : 
     : "i"(&readyTcbQueue.pTcb),"i"(&(((BragOsTcbWrapper*)0)->psp)) 
     :); 
} 

我认为这会为你

+0

谢谢你的回答。好的解决方案我会发布另一个解决方案,无需额外的指针,但费用单跳指令 – brag

0

工作,因为在裸体的功能扩展ASM不支持gcc/clang

裸体 此属性允许编译器构造必要的函数声明,同时允许函数体为 汇编代码。指定的函数不会有编译器生成的序列/尾语 。只有基本汇编语句可以安全地包含在裸函数中(请参阅基本汇编)。当使用 扩展asm或基本asm和C代码的混合可能似乎工作时,它们不能依赖于可靠地工作并且不被支持。 https://gcc.gnu.org/onlinedocs/gcc/ARM-Function-Attributes.html#ARM-Function-Attributes

我们可以使用普通的C++函数和跳/裸体从

typedef unsigned long U32; 

__attribute__((naked)) void ePendSV(){ 
    asm volatile("\ 
     mrs r0, PSP \n\ 
     stmdb r0!, {r4-r11,lr} \n\ 
     b realSwitchContext"); // this solution costs single jump instruction named **b**. It's valid for ARM-assembly in this case. 
           // Stack pointer has been saved in r0 register. we dont care about stack for now. 
} 

extern "C" void realSwitchContext(U32 psp){ 
    readyTcbQueue.pTcb.runTcb->psp = psp; 
    BragOsTcb *tcb = readyTcbQueue.pTcb.readyTcb; 
    readyTcbQueue.pTcb.runTcb = tcb; 
    psp = tcb->psp; 
    asm volatile("\ 
     ldmia %0!, {r4-r11,lr} \n\ 
     msr PSP, %0 \n\ 
     bx lr" : : "r"(psp)); // we just changed stack pointer then can return here and don't care about C++ stacked data 
} 

调用它有一个在上面执行,因为两个堆栈指针的一些bug - MSP和PSP。如果C++将使用堆栈,MSP将被最后的bx lr指令破坏。下面的解决方案是正确的,但花费2“昂贵”的指令 - BL(呼叫)和BX LR(返程)

__attribute__((naked)) void ePendSV(){ 
    asm volatile("\ 
     mrs r0, PSP \n\ 
     stmdb r0!, {r4-r11,lr} \n\ 
     bl realSwitchContext \n\ 
     ldmia r0!, {r4-r11,lr} \n\ 
     msr PSP, r0 \n\ 
     bx lr"); 
} 

extern "C" U32 realSwitchContext(U32 psp){ 
    readyTcbQueue.pTcb.runTcb->psp = psp; 
    BragOsTcb *tcb = readyTcbQueue.pTcb.readyTcb; 
    readyTcbQueue.pTcb.runTcb = tcb; 
    return tcb->psp; 
} 
+0

它看起来很有吸引力。不过,我认为你可能会将堆栈置于未定义的状态。编译器生成的代码将内容存储在realSwitchContext中的堆栈中,并且由于您切换了堆栈指针,因此您将离开先前堆栈的堆栈上的各种数据。此外,堆栈指针本身由编译器生成的代码更改。我不认为你可以依靠这是一个很好的解决方案(除非你确保始终保持正确的堆栈指针) –

+0

初始堆栈指针由第一条指令mrs r0,PSP保存。 r0是名为psp的realSwitchContext的第一个参数,然后它将被保存到C++对象中。编译器可以自由地更改堆栈(PSP),因为它将在稍后由msr PSP,%0(%0是从另一个C++对象读取的psp)在另一个调用ePendSV中恢复。在恢复堆栈指针instr后,bx lr(从函数指令返回)完全放置。所以编译器也不能在这里更改堆栈ptr。 – brag

+0

对不起,我错了。有两个堆栈PSP和MSP。保存在asm中的寄存器保存到PSP堆栈中。但C++将与MSP协同工作。正确的方法是调用而不是跳转并将所有asm代码移动到ePendSV,这会花费单个调用指令和单个返回指令。 – brag