本文轉(zhuǎn)自公眾號(hào),歡迎關(guān)注
https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw
0.背景
本文記錄很久之前在一個(gè)項(xiàng)目中遇到的”幽靈問(wèn)題”,結(jié)構(gòu)體讀寫(xiě)異常,雖然最終結(jié)論很簡(jiǎn)單,遇到過(guò)類(lèi)似問(wèn)題或者了解對(duì)應(yīng)知識(shí)點(diǎn)的可能一眼就知道了,但是沒(méi)遇到過(guò)的可能會(huì)花費(fèi)很多時(shí)間去定位甚至無(wú)從下手。這就是經(jīng)驗(yàn)的重要性,所以特分享出這篇文章。結(jié)論本身沒(méi)有很大的技術(shù)含量,但是中間涉及的思想,態(tài)度,解決問(wèn)題的思路,過(guò)程,如何形成標(biāo)準(zhǔn),避免類(lèi)似問(wèn)題等等確是我們嵌入式開(kāi)發(fā)中的共性問(wèn)題。
1.問(wèn)題回顧
1.1歷史問(wèn)題1 在不同地方,結(jié)構(gòu)體訪問(wèn)按照不同對(duì)齊方式訪問(wèn),開(kāi)始懷疑keil編譯器的問(wèn)題。之前還換了keil的不同版本去試都是一樣。
之前can驅(qū)動(dòng)在改了某版本代碼后突然收不到數(shù)據(jù),調(diào)試記錄如下:寫(xiě)和讀時(shí)結(jié)構(gòu)體對(duì)齊方式不一樣。
mdk未顯式指定結(jié)構(gòu)體對(duì)齊方式時(shí),通過(guò).訪問(wèn)成員變量,可能不同地方對(duì)齊方式不一樣。
Mdk版本v5.xx ARM CC編譯器V5.06
寫(xiě)結(jié)構(gòu)體成員CAN1->sFIFOMailBox[1].RIR 查看對(duì)應(yīng)的匯編代碼是STR r0 [SP,#0x0C]
即RIR成員變量偏移地址是0xC,此時(shí)采用自然對(duì)齊非壓縮方式。寫(xiě)進(jìn)去的值是0x00 22 E2 F0。
讀結(jié)構(gòu)體成員CAN1->sFIFOMailBox[1].RIR 查看對(duì)應(yīng)的匯編代碼是LDR r0 [SP,#0x0A]
即RIR成員變量偏移地址是0xA。與寫(xiě)時(shí)偏移地址不一樣,此時(shí)采用了壓縮方式, 讀出來(lái)的值是E2 EF A5 A5,偏移了2字節(jié)。查看內(nèi)存,實(shí)際內(nèi)存的值是對(duì)的,只是結(jié)構(gòu)體訪問(wèn)時(shí)對(duì)應(yīng)匯編代碼成員變量的的偏移地址不對(duì),導(dǎo)致解析錯(cuò)誤,如果按寫(xiě)入時(shí)的偏移0x0C解析讀到的值是0x0022E2EF就是正確的。
解決辦法:暫時(shí)不確定是編譯器問(wèn)題還是配置問(wèn)題,手動(dòng)顯式設(shè)置結(jié)構(gòu)體對(duì)齊方式,可解決該問(wèn)題。
1.2歷史問(wèn)題2 某些結(jié)構(gòu)體增加#pragma pack(1)導(dǎo)致后導(dǎo)致系統(tǒng)異常。
當(dāng)時(shí)修改代碼后未復(fù)現(xiàn),沒(méi)有記錄現(xiàn)場(chǎng)。
2問(wèn)題分析過(guò)程
查找問(wèn)題起始點(diǎn)
前面花了差不多一天時(shí)間去對(duì)比代碼逐漸刪除,最終定位到driver_can.h增加以下代碼
就有問(wèn)題不加就沒(méi)問(wèn)題
#pragma pack(1)
typedef struct
{
uint16_t rx_in_u16; /**< 接收緩沖區(qū)寫(xiě)入指針 */
uint16_t rx_out_u16; /**< 接收緩沖區(qū)讀出指針 */
uint16_t rx_len_u16; /**< 接收緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t tx_in_u16; /**< 發(fā)送緩沖區(qū)寫(xiě)入指針 */
uint16_t tx_out_u16; /**< 發(fā)送緩沖區(qū)讀出指針 */
uint16_t tx_len_u16; /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t rxbuf_len_u16; /**< 接收緩沖區(qū)大小 */
uint16_t txbuf_len_u16; /**< 發(fā)送緩沖區(qū)大小 */
driver_can_data_t *rx_buf_pt; /**< 接收緩沖區(qū) */
driver_can_data_t *tx_buf_pt; /**< 發(fā)送緩沖區(qū) */
}driver_can_t;
進(jìn)一步驗(yàn)證
找到出現(xiàn)問(wèn)題的代碼后就一步步跟蹤
在頭文件中driver_can.h中定義了
在osapi.c中 include “driver_can.h”
導(dǎo)致以下代碼 綠色語(yǔ)句執(zhí)行后出錯(cuò)。
在osapi.c中 不包含 driver_can.h
上述現(xiàn)象無(wú)
調(diào)試分析
用仿真器跟蹤調(diào)試對(duì)比有問(wèn)題和無(wú)問(wèn)題的代碼執(zhí)行時(shí)的環(huán)境(變量地址 變量值等)
先包含driver.h 有問(wèn)題時(shí)情況如下:
Osapi.c中如下代碼執(zhí)行
可以看出進(jìn)入函數(shù)uxTaskGetSystemState執(zhí)行前pxTaskStatusArray的eCurrentState和uxCurrentPriority只相差1,說(shuō)明是pack(1)模式
進(jìn)入函數(shù)uxTaskGetSystemStat后(在task.c中) 看到紅色部分變了,pxTaskStatusArray的eCurrentState和uxCurrentPriority相差4,說(shuō)明是非pack(1)模式
在后面繼續(xù)給uxCurrentPriority等成員賦值時(shí)實(shí)際上溢出了,因?yàn)閭魅雙xTaskStatusArray的是復(fù)函數(shù)malloc出來(lái)的,所以這里溢出將導(dǎo)致malloc的鏈表關(guān)系破壞導(dǎo)致整個(gè)堆環(huán)境破壞,后面問(wèn)題會(huì)蔓延最終導(dǎo)致災(zāi)難性錯(cuò)誤。
如果上面的pxTaskStatusArray不是malloc出來(lái)的而是棧中的臨時(shí)變量則會(huì)導(dǎo)致棧破壞,最終問(wèn)題也可能蔓延導(dǎo)災(zāi)難性的錯(cuò)誤。
而不包含driver.h時(shí)
進(jìn)入函數(shù)uxTaskGetSystemState前
進(jìn)入函數(shù)后 沒(méi)有變
最終原因
從上可以看出,因?yàn)閜xTaskStatusArray對(duì)應(yīng)結(jié)構(gòu)體是沒(méi)有TaskStatus_t顯示指定對(duì)齊模式的,
Osapi包含了driver.h的#pragma pack(1)所以osapi整個(gè)文件中沒(méi)有顯示指定的對(duì)齊模式的結(jié)構(gòu)體都按照pack(1)對(duì)齊,而task.c中按照默認(rèn)對(duì)齊方式(4字節(jié)),所以導(dǎo)致錯(cuò)誤。
實(shí)際上這里是#pragma pack(1)的用法錯(cuò)誤 正確用法見(jiàn)《總結(jié)》
上述分析過(guò)程在keil中也是一樣的,所以之前懷疑的keil編譯有問(wèn)題是錯(cuò)誤的,跟編譯器沒(méi)有關(guān)系,是pack(1)指令使用錯(cuò)誤導(dǎo)致。
3.問(wèn)題回顧
回顧問(wèn)題一
為什么不同地方結(jié)構(gòu)體訪問(wèn)不同?
是因?yàn)楫?dāng)時(shí)有些地方的頭文件中增加了#pragma pack(1),有些c文件包含了該頭文件,有寫(xiě)c文件沒(méi)有包含該文件。在包含了該頭文件的c文件中所有沒(méi)有顯示指定對(duì)齊模式的結(jié)構(gòu)將會(huì)按照pack(1)模式,沒(méi)有包含該頭文件的c文件中則會(huì)按照編譯器默認(rèn)的對(duì)齊模式。所以導(dǎo)致不同c文件對(duì)齊模式不一樣,關(guān)鍵是看有包含的頭文件中有#pragma pack(1)
為什么對(duì)結(jié)構(gòu)體顯示的指定對(duì)齊模式后就沒(méi)問(wèn)題?
#pragma pack(1)的含義是: c文件#pragma pack(1)指令后所有沒(méi)有顯示指定對(duì)齊模式的結(jié)構(gòu)體都會(huì)按照pack(1)對(duì)齊。
對(duì)于顯示指定對(duì)齊模式的結(jié)構(gòu)體按照指定對(duì)齊模式,所以顯示指定后不受#pragma pack(1)影響
回顧問(wèn)題二
為什么不知何故加了些代碼就好了?
因?yàn)橛袉?wèn)題的c代碼中沒(méi)有包含有#pragma pack(1)的頭文件,或者結(jié)構(gòu)體顯示的增加了對(duì)齊模式。
總結(jié)
結(jié)構(gòu)體對(duì)齊方式的指定有兩種,推薦使用第一種
l第一種: 直接對(duì)結(jié)構(gòu)體顯式定義對(duì)齊模式 這種方式一般使用于頭文件申明時(shí)
對(duì)于支持gcc屬性擴(kuò)展的編譯器(IAR KEIL新版本都支持) 使用
例如
typedef struct __attribute__ ((__packed__)) loop_to_channel
{
uint8_t loop;
gpio_ch_e ch;
} loop_to_channel_t;
對(duì)于IAR還可以使用__packed
/**
* struct driver_can_status_t
* CAN狀態(tài)結(jié)構(gòu)體.
*/
typedef __packed struct
{
uint8_t send_err; /**< 發(fā)送錯(cuò)誤幀計(jì)數(shù) */
uint8_t rcv_err; /**< 接收錯(cuò)誤幀計(jì)數(shù) */
uint32_t send_frames; /**< 發(fā)送幀數(shù) */
uint32_t rcv_frames; /**< 接受幀數(shù) */
uint32_t esr; /**< 狀態(tài)寄存器 */
}driver_can_status_t;
l第二種: #pragma pack(1) 這種方式一般使用于c文件中對(duì)本文件設(shè)置后所有地方生效
這種方式一定要注意恢復(fù)設(shè)置
正確示例
#pragma pack(push)
#pragma pack(1)
typedef struct
{
uint16_t rx_in_u16; /**< 接收緩沖區(qū)寫(xiě)入指針 */
uint16_t rx_out_u16; /**< 接收緩沖區(qū)讀出指針 */
uint16_t rx_len_u16; /**< 接收緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t tx_in_u16; /**< 發(fā)送緩沖區(qū)寫(xiě)入指針 */
uint16_t tx_out_u16; /**< 發(fā)送緩沖區(qū)讀出指針 */
uint16_t tx_len_u16; /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t rxbuf_len_u16; /**< 接收緩沖區(qū)大小 */
uint16_t txbuf_len_u16; /**< 發(fā)送緩沖區(qū)大小 */
driver_can_data_t *rx_buf_pt; /**< 接收緩沖區(qū) */
driver_can_data_t *tx_buf_pt; /**< 發(fā)送緩沖區(qū) */
}driver_can_t;
#pragma pack(pop)
錯(cuò)誤示例
#pragma pack(1)
typedef struct
{
uint16_t rx_in_u16; /**< 接收緩沖區(qū)寫(xiě)入指針 */
uint16_t rx_out_u16; /**< 接收緩沖區(qū)讀出指針 */
uint16_t rx_len_u16; /**< 接收緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t tx_in_u16; /**< 發(fā)送緩沖區(qū)寫(xiě)入指針 */
uint16_t tx_out_u16; /**< 發(fā)送緩沖區(qū)讀出指針 */
uint16_t tx_len_u16; /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小 */
uint16_t rxbuf_len_u16; /**< 接收緩沖區(qū)大小 */
uint16_t txbuf_len_u16; /**< 發(fā)送緩沖區(qū)大小 */
driver_can_data_t *rx_buf_pt; /**< 接收緩沖區(qū) */
driver_can_data_t *tx_buf_pt; /**< 發(fā)送緩沖區(qū) */
}driver_can_t;
審核編輯:湯梓紅
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19122瀏覽量
305109 -
keil
+關(guān)注
關(guān)注
68文章
1213瀏覽量
166874 -
編譯器
+關(guān)注
關(guān)注
1文章
1634瀏覽量
49128 -
結(jié)構(gòu)體
+關(guān)注
關(guān)注
1文章
130瀏覽量
10841
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論