問題現象
調試sp02
LLVM編譯的可執行程序在Linux模擬器上運行,無法執行結束。在Windows模擬器可以正常執行(有亂碼)。
Linux模擬器運行結果
*** TEST 2 ***
INIT - xxos_task_wake_after - yielding processor
PREEMPT - xxos_task_delete - deleting selfINIT - suspending TA2 while middle task on a ready chainTA1 - xxos_task_wake_after - sleep 1 secondTA2 - xxos_task_wake_after - sleep 1 minuteTA3 - xxos_task_wake_after - sleep 5 secondsTA1 - xxos_task_ident - tid of TA2 (0x.8x)
TA1 - xxos_get_classic_name - id -> name of TA2 OK
TA1 - xxos_task_ident - tid of TA3 (0x.8x)
Windows模擬器運行結果
*** TEST 2 ***
INIT - xxos_task_wake_after - yielding processor
PREEMPT - xxos_task_delete - deleting selfINIT - suspending TA2 while middle task on a ready chainTA1 - xxos_task_wake_after - sleep 1 secondTA2 - xxos_task_wake_after - sleep 1 minuteTA3 - xxos_task_wake_after - sleep 5 secondsTA1 - xxos_task_ident - tid of TA2 (0x.8x)
TA1 - xxos_get_classic_name - id -> name of TA2 籓K
TA1 - xxos_task_ident - tid of TA3 (0x.8x)
TA1 - xxos_task_set_priority - set TA3's priority to 2TA1 - xxos_task_suspend - suspend TA2TA1 - xxos_task_delete - delete TA2TA1 - xxos_task_wake_after - sleep for 5 secondsTA3 - xxos_task_delete - delete self*** END OF TEST 2 ***
調試sp03
調試前
Linux模擬器
*** TEST 3 ***
Windows模擬器
*** TEST 3 ***
狻籃0
程序卡在vprintk()中, sp04也是
嘗試解決
- printk打印加上換行
- 修改
print_time()
和put_name()
函數:分別在他們調用的printk打印結尾加上換行。
修改之後Linux模擬器多打印了幾行亂碼。看來也不是因爲緩衝沒及時flush的原因。
問題鎖定:printk加參數就崩
既然知道是printk()
和vprintk()
的問題(程序卡死在vprintk()
中),那麼就把問題出現的條件簡單化,在程序中以從簡單到複雜的形式來調用printk()
,以逐步排查問題。首先嚐試直接打印一個常量字符串,沒問題。接下來嘗試打印一個局部整形變量,出問題了。多試幾次之後發現,只要printk()
以加參數打印的形式就會崩。
int a = 10;
printk("a=%d\n", a);
打印結果竟然是
a=8411292
打印的看起來是一個地址。改vprintk還是編譯器?????
問題剖析
看vprintk彙編代碼
- U0是第一個參數format字符串的地址,因爲是字符串常量,該地址在數據段。U1存的是第二個參數va_list的地址,在棧中。
printk的彙編有問題
_printk:
0b000480: XR39=SER || YR39=U9
0b000482: [U8+=-2,-1]=XYR39
0b000484: U9=U8
0b000485: U8=U8+-2
0b000486: XR0=[U9+3]
0b000487: [U9+0]=XR0
0b000488: U0=U9+0
0b000489: XR1=U0
0b00048a: XR1=R1+1 //這裏把第二個參數的位置指錯了
0b00048b: U0=XR0
0b00048c: U1=XR1
0b00048d: CALL 0x0b001400
0b00048f: U8=U9
0b000490: XYR39=[U8+1,1] || U8=U8+2
0b000493: U9=XR39 || YSER=R39
0b000495: RET
printk源碼:
/*
* printk
*
* Kernel printf function requiring minimal infrastrure.
*/
void printk(const char *fmt, ...)
{
va_list ap; /* points to each unnamed argument in turn */
va_start(ap, fmt); /* make ap point to 1st unnamed arg */
vprintk(fmt, ap);
va_end(ap); /* clean up when done */
}
printk()
中va_start(ap, fmt);
會把ap
指向fmt
的下一個地址,以前的編譯器編譯的結果不復制參數,所以沒問題。這個編譯器會複製參數,但是對於這種可變參數的,又只複製了第一個參數也就是fmt
,之後把ap
指向複製後的fmt
的下一個地址,就錯了。如圖1所示。
問題實質
因此錯誤就是printk()
處理參數錯誤,對可變參數列表沒有進行復制,而在使用時又是按照已複製的形式來使用,因此使用了錯誤的參數,打印的結果自然也是錯的,之前打印的是SER寄存器入棧之前的值。再深一層的原因就需要分析編譯器的代碼了,我的猜想可能是這個編譯器處理可變參數列表的情況就會出錯,當然只是瞎猜,有機會可以測試一下。
解決問題
爲了解決這個問題,這裏我們調整va_start(ap, fmt)
的實現。使得ap的位置指向複製後參數的往後 4 個地址,也即參數被複制之前的地址。
目前這種方法看起來是可行的,但是絕對不符合邏輯,編譯器對我們來說不透明瞭,我們不應該知道編譯器是怎麼傳參的。