从裸体asm函数访问C ++非POD类数据

时间:2015-10-16 16:17:34

标签: c++ assembly

是否可以使这个混合的c ++ / asm函数符合标准? 函数ePendSV()必须具有以下布局:

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 是一个简单的struct对象,只有两个指向 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
}

谢谢!

2 个答案:

答案 0 :(得分:0)

所以你想要的是从一个没有参数的裸函数访问BragOsTcb的psp成员,还有更多它作为硬件中断处理程序调用,所以你不能添加任何其他代码来加载地址您。 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;
....
};

现在你可以这样做:


__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))
        : );
}

我认为这对你有用

答案 1 :(得分:0)

由于gcc / clang

不支持裸函数中的扩展asm
  

裸       此属性允许编译器构造必需的函数声明,同时允许函数体   汇编代码。指定的函数不会有序言/尾声   编译器生成的序列。只有基本的asm语句才能   安全地包含在裸功能中(参见Basic Asm)。使用时   扩展的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
}

上面的实现中存在一些错误,因为有两个堆栈指针--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;
}