用dw做淘客網(wǎng)站的步驟南京百度推廣開戶
記一次調(diào)試過程
這是一個(gè)在 arm 架構(gòu)上的 RTOS 上的調(diào)試過程。問題現(xiàn)象為使用 thumb 指令集的 libgcc 庫的情況下,浮點(diǎn)運(yùn)算隨機(jī)出錯(cuò)。經(jīng)過一番追蹤調(diào)試,逐步縮小問題范圍,最后定位問題,成功解決。
場(chǎng)景
在某款的國產(chǎn) RTOS 上,由于客戶應(yīng)用需要,使用了thumb 指令集編譯的 libgcc 的庫,導(dǎo)致了同時(shí)運(yùn)行了 arm 指令集和 thumb 指令集的代碼。原本的 RTOS 未設(shè)計(jì)運(yùn)行 thumb 指令的代碼,也沒有嚴(yán)格測(cè)試過,所以本次暴露了浮點(diǎn)運(yùn)算偶爾出錯(cuò)的問題。
測(cè)試代碼
客戶精簡(jiǎn)過的暴露問題代碼如下
unsigned long long ulTa = 0x100;
unsigned long long ulTb = 0x2001;
double dRea, dReb;
double dRes;while (1) {= (double)ulTa;dReb = (double)ulTb;dRes = dRea / dReb;if ((dRea == 0.0) || (dReab == 0.0) || (c <= 0.01f)) {printf("error!!\r\n");}}
這段代碼的邏輯是強(qiáng)轉(zhuǎn)了兩個(gè) unsigned long long 類型,然后進(jìn)行除法運(yùn)行,最后判斷各個(gè)變量的結(jié)果是否符合預(yù)期,否則就報(bào) error。
問題分析
抓取問題中的關(guān)鍵點(diǎn):
- 和浮點(diǎn)運(yùn)算有關(guān),是不是我們的RTOS對(duì)于浮點(diǎn)運(yùn)算的寄存器沒有處理好
- 簡(jiǎn)單的代碼反復(fù)運(yùn)行只是偶爾出問題,而不是固定每次出問題,那么考慮其他外部的影響,首先考慮的是中斷。
問題調(diào)試
bug調(diào)試的指導(dǎo)思想是,仔細(xì)觀察問題剖析問題,提出懷疑一些原因,并修改代碼驗(yàn)證。
步驟1 剖析問題
靠近問題的實(shí)質(zhì),分析問題:既然計(jì)算出錯(cuò),那么看看出錯(cuò)的時(shí)候的變量是什么樣的。經(jīng)過一些調(diào)試打印發(fā)現(xiàn) 3 個(gè) double 類型的變量都出錯(cuò)過。出錯(cuò)的時(shí)候,打印這些變量對(duì)應(yīng)地址存儲(chǔ)的值,發(fā)現(xiàn)這些內(nèi)存的值有 0 存在。因?yàn)?double 變量使用 8 bytes 的空間存放的,這些空間里存放的不是實(shí)際值,而是分符號(hào)位,指數(shù)位等。
根據(jù)這個(gè)現(xiàn)象,我有理由懷疑是某些時(shí)候的強(qiáng)轉(zhuǎn)沒有成功。
步驟2 證明和中斷的相關(guān)性
進(jìn)行關(guān)中斷測(cè)試,在關(guān)閉中斷的情況下,長(zhǎng)時(shí)間跑了測(cè)試,時(shí)間足夠長(zhǎng),發(fā)現(xiàn)并沒有出錯(cuò)。那么證明的確和中斷有關(guān)系。
步驟3 證明測(cè)試代碼的邏輯是沒有問題
排查其他問題:使用 arm 指令集的 libgcc 進(jìn)行測(cè)試,使用同樣的代碼,時(shí)間足夠長(zhǎng),也沒有出錯(cuò)。那么證明測(cè)試代碼的邏輯是沒有問題的。
陷入了第一個(gè)瓶頸
確定中斷有問題,那么推測(cè)偶爾出問題肯定是恰好某個(gè)時(shí)刻的中斷踩到了特殊位置。修改代碼關(guān)注是否是中斷返回立即出錯(cuò),打印分析被中斷打斷處的指令,觀察是否是固定的某個(gè)指令。經(jīng)過漫長(zhǎng)的調(diào)試,觀察到的確有規(guī)律。
分析長(zhǎng)時(shí)間的出錯(cuò)前的 PC 指針的位置,很多是靠近 thumb 指令集里面的 IT block 指令。必須敏銳地抓住這個(gè)點(diǎn)。類似下面的 it eq
指令。
001007d4 <__aeabi_ul2d>:1007d4: ea50 0201 orrs.w r2, r0, r11007d8: bf08 it eq1007da: 4770 bxeq lr1007dc: b530 push {r4, r5, lr}1007de: f04f 0500 mov.w r5, #01007e2: e00a b.n 1007fa <__aeabi_l2d+0x16>
簡(jiǎn)化問題
看手冊(cè)學(xué)習(xí) it block 指令。
// 學(xué)習(xí)過程不表
證明是 it 指令的問題
調(diào)試指導(dǎo)思想是:縮小問題的范圍
想辦法直接運(yùn)行這個(gè)指令,觀察是否出錯(cuò)。我使用內(nèi)聯(lián)匯編實(shí)現(xiàn),使用偽代碼解釋代碼邏輯
r1 = 0x10;
r2 = 0x10;
r3 = 0x20;
r4 = 0x40;r4 = r4 - r2; //運(yùn)行后 r4 = 0x30// itte ge , 上一行指令會(huì)改變 cpsr 的 flag 標(biāo)志位,
// 若 ge: r1 = r1 + (r4 << 1) //運(yùn)行后 r1 = 0x70;
// 若 ge: r2 = r2 + 0x10;
// 若 lt: r4 = r4 + 0x10;
代碼如下:
__attribute__((target("thumb"))) void thumb_ins(void);
void thumb_ins(void) {int ia = 0;int ib = 0;int ic = 0;int cnt = 0;while (1) {ia = 0;ib = 0;ic = 0;asm volatile ("mov r1, #0x10");asm volatile ("mov r3, #0x20");asm volatile ("mov r2, #0x10");asm volatile ("mov r4, #0x40");asm volatile ("subs r4, r4, r2");asm volatile ("itte ge");asm volatile ("addge.w r1, r1, r4, lsl #1");asm volatile ("addge r2, r2, #0x10");asm volatile ("addlt r4, r4, #0x10");asm volatile ("mov %0, r1" : "=r" (ia));asm volatile ("mov %0, r2" : "=r" (ib));asm volatile ("mov %0, r4" : "=r" (ic));if ((ia != 0x70) || (ib != 0x20) || (ic != 0x30)) {printf("error!!!");printf(" r1 [0x%x] r2 [0x%x] r4[0x%x]\r\n", ia, ib, ic);}if (cnt ++ > 9000000) {printf(" r1 [0x%x] r2 [0x%x] r4[0x%x]\r\n", ia, ib, ic);cnt = 0;}}
}
經(jīng)過運(yùn)行測(cè)試,這個(gè)代碼運(yùn)行時(shí),也會(huì)打印出 error;關(guān)閉中斷進(jìn)行測(cè)試,則運(yùn)行不會(huì)打印 error。則完全證明了是 it 指令執(zhí)行的問題。并且發(fā)現(xiàn)了出錯(cuò)時(shí), it block 控制的條件允許代碼全部沒有執(zhí)行,包括2條判斷條件相反的指令都沒有執(zhí)行。
陷入了第二個(gè)瓶頸 分析 it 指令執(zhí)行出錯(cuò)的原因
arm 手冊(cè)上關(guān)于這個(gè) it 指令從異常返回的描述:
On a branch or an exception return, if PSTATE.IT is set to a value that is not consistent with the instruction stream being branched to or returned to, then instruction execution is CONSTRAINED UNPREDICTABLE.
我們需要關(guān)注到異常返回的地址和異常返回時(shí) PSTATE.IT 的標(biāo)志狀態(tài)。
- 經(jīng)過調(diào)試分析,已經(jīng)確認(rèn)我們的 RTOS 在異常保存上下文中 cpsr 的值是正確的。使用 msr 將其恢復(fù)也是正確的。也確認(rèn)了我們RTOS配置的返回地址也是正確的。滿足手冊(cè)中提到的注意事項(xiàng),問題陷入僵局。
- 試圖對(duì)比 Linux 源碼中的異常保存和恢復(fù)過程中,對(duì)于 thumb 指令是否有特殊處理,然而并沒有收獲。
問題突破
經(jīng)過艱難的調(diào)試之后,終于有了新的突破
我們知道,在異常返回時(shí),是由硬件自動(dòng)將 spsr 寄存器中的值恢復(fù)到 cpsr。我在設(shè)置PC跳轉(zhuǎn)之前,讀出 spsr 的值,保存到內(nèi)存中,然后有了驚人的發(fā)現(xiàn):此時(shí) spsr 寄存器中的內(nèi)容和上下文結(jié)構(gòu)體中的值不一致,而且是關(guān)鍵的 IT block 的信息丟失。那么斷定是恢復(fù) spsr 的時(shí)候出錯(cuò)了。
經(jīng)過簡(jiǎn)單調(diào)試,和 Linux 進(jìn)行對(duì)比,發(fā)現(xiàn)了問題的根源。
問題解決
我們的 RTOS 恢復(fù) spsr 使用的指令為 msr spsr, r1
,編譯生成的指令為 83ce0ae8: e169f001 msr SPSR_fc, r1
。而 Linux 使用 msr spsr_cxsf, r1
, 編譯生成的指令為 83ce0ae8: e16ff001 msr SPSR_fsxc, r1
。我的 RTOS 在恢復(fù) spsr 中,丟失了關(guān)鍵的 s 和 x 域的內(nèi)容:
- c 指 CPSR中的control field ( PSR[7:0])
- f 指 flag field (PSR[31:24])
- x 指 extend field (PSR[15:8])
- s 指 status field ( PSR[23:16])
而恰好 IT block 信息存放在 [26:25] + [15:10]
IT[1:0], bits [26:25]
IT block state bits for the T32 IT (If-Then) instruction. See IT[7:2] for explanation of this field.
IT[7:2], bits [15:10]
IT block state bits for the T32 IT (If-Then) instruction. This field must be interpreted in two parts. ? IT[7:5] holds the base condition for the IT block. The base condition is the top 3 bits of the
condition code specified by the first condition field of the IT instruction.
? IT[4:0] encodes the size of the IT block, which is the number of instructions that are to be
conditionally executed, by the position of the least significant 1 in this field. It also encodes the value of the least significant bit of the condition code for each instruction in the block.
The IT field is 0b00000000 when no IT block is active.
調(diào)試經(jīng)驗(yàn)總結(jié)
- 分析問題,縮小問題范圍
- 敏銳觀察,抓住靈感
- 驗(yàn)證寄存器的值是否正確不能讀中間狀態(tài),必須緊貼使用之前進(jìn)行驗(yàn)證
- 相信硬件,在計(jì)算機(jī)的世界沒有上帝