2015-10-16 107 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