筆者接著來介紹一下Bootloader的跳轉代碼以及優化
1、跳轉代碼理解
跳轉代碼可能要涉及到芯片架構的知識,要跳轉到對應的位置,還要設置相關的SP 堆棧指針,具體可以參考筆者這篇文章BootLoader的理解與實現。
STM32的跳轉代碼如下所示:
u32 ApplicationAddress = 0x08008000; //app 地址
typedef void (*pFunction)(void); //函數指針
void Jump_Used_Main(void)
{printf("\r\nboot2-------- Jump APP --------- \r\n");/* 判斷棧頂 是否位于 0x20000000 128K */if ( ((*(__IO uint32_t*)ApplicationAddress) >= 0x20000000) &&((*(__IO uint32_t*)ApplicationAddress) <= 0x20010000)){ JumpAddress = *(__IO uint32_t*) (ApplicationAddress +4);Jump_To_Application = (pFunction) JumpAddress;/*close the interrupt*/_disable_interrupr();__set_MSP(*(__IO uint32_t*) ApplicationAddress);Jump_To_Application();}else{ while(1){printf("\r\nboot2 error\r\n");}}
}
可以看到__set_MSP(* (__IO uint32_t *) ApplicationAddress);這行代碼中,在取地址里面的內容時,增加了__IO的選項,
#define __IO volatile
保證是從內存里面讀出來的SP棧指針的數據,然后設置到MSP,否則可能導致SP設置錯誤,程序跑飛。
2、跳轉代碼編譯優化
筆者在實際開發的過程中,遇到了一個跳轉過去就崩掉的情況,單步調試,發現到Set_MSP就崩掉了,很是奇怪,通過匯編一查看,就很明顯了。
- NXP LPC的單片機,
- arm-noen-eabi-gcc的編譯器。
typedef struct addr_manager_struct
{xxxxxxx;u32 image_load_addr;u32 image_exec_addr;
}addr_manager_t;
u8 main_jump(addr_manager_t* info)
{u32 *image_addr = (u32*)info->image_load_addr;u32 *sp = (u32*) image_addr [0];u32 *jump = (u32*) image_addr [1];disable_interrupt();set_msp((u32)sp);(*jump)();return 0;
}
- 上述在設置完SP之后,放到r2寄存器里面,
- 然后獲取jump地址,放到r3里面,
- 之后禁止中斷修改了r2,然后禁止中斷
- 然后就把r2傳到SP里面,
- 造成跑飛,可能是一個不存在的地址
有人說sp設置的時候需要加volatile,即使改成下面的函數,也沒用效果
void jump_main(addr_manager_t* info)
{u32 *image_addr = (u32*)info->image_load_addr;u32 *sp = (u32*) image_addr [0];u32 *jump = (u32*) image_addr [1];disable_interrupt();set_msp((volatile u32)sp);(*jump)();
}
接著再繼續改,直
- 接通過Image指針取內容,然后去獲取到SP值,然后就可以了,
- 看來是禁止中斷的這個函數所影響,
- 禁止中斷如果不報存返回值,則不會對SP的值產生影響
void jump_main(addr_manager_t* info)
{u32 *image_addr = (u32*)info->image_load_addr;u32 *sp = (u32*) image_addr [0];u32 *jump = (u32*) image_addr [1];disable_interrupt();set_msp(*((volatile u32*)image_addr));(*jump)();
}
接著我們再嘗試一種方法,就是將禁止中斷函數移動位置,看一下情況,發現也是可以的。
void jump_main(addr_manager_t* info)
{disable_interrupt();u32 *image_addr = (u32*)info->image_load_addr;u32 *sp = (u32*) image_addr [0];u32 *jump = (u32*) image_addr [1];set_msp((u32)sp);(*jump)();
}
然后我們看一下set sp的匯編函數,也并沒有指明操作的寄存器,很可能是GCC編譯器優化的bug。
inline void set_sp(u32 sp_value)
{asm volatile ("msr msp, %0" : "=r"(sp_value))
}
然后我們再看看armcc編譯器的結果,沒有任何問題。
- armcc對于禁止中斷的返回值沒有處理,
- 而GCC處理了然后導致了問題(將其中斷保存值填到r2里面,然后r2的sp值被覆蓋),
- 其實應用層如果沒有對返回值進行處理,即使函數有返回值,編譯器優化會將其處理掉。顯然GCC的編譯器優化做的還是差一點。