LLVM調試記錄 - 20180409

問題現象

調試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彙編代碼

  1. 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所示。

圖 1 vprintk()函數調用棧

圖1 printk()函數調用棧

問題實質

因此錯誤就是printk()處理參數錯誤,對可變參數列表沒有進行復制,而在使用時又是按照已複製的形式來使用,因此使用了錯誤的參數,打印的結果自然也是錯的,之前打印的是SER寄存器入棧之前的值。再深一層的原因就需要分析編譯器的代碼了,我的猜想可能是這個編譯器處理可變參數列表的情況就會出錯,當然只是瞎猜,有機會可以測試一下。

解決問題

爲了解決這個問題,這裏我們調整va_start(ap, fmt)的實現。使得ap的位置指向複製後參數的往後 4 個地址,也即參數被複制之前的地址。
目前這種方法看起來是可行的,但是絕對不符合邏輯,編譯器對我們來說不透明瞭,我們不應該知道編譯器是怎麼傳參的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章