跳到主要內容

誰在呼叫我?不同的backtrace實作說明好文章

今天下班前一個同事問到:如何在Linux kernel的function中主動印出backtrace以方便除錯?

寫過kernel module的人都知道,基本上就是用dump_stack()之類的function就可以作到了。但是dump_stack()的功能是如何作到的呢?概念上其實並不難,慣用手法就是先觀察stack在function call時的變化(一般OS或計組教科書都有很好的說明,如果不想翻書,可以參考這篇),然後將對應的return address一層一層找出來後,再將對應的function名稱印出即可(透過執行檔中的section去讀取函式名稱即可,所以要將KALLSYM選項打開)。在userspace的實作可參考Jserv介紹過的whocallme或對岸好手實作過的backtrace(),都是針對x86架構的很好說明文章。

不過從前面兩篇文章可以知道,只要知道編譯器的calling convention,就可以實作出backtrace,所以是否GCC有提供現成的機制呢?Yes, that is what __builtin_return_address() for!! 可以參考這篇文章。該篇文章還提到了其他可以拿來實作功能更齊全的backtrace的程式庫,在了解了運作原理後,用那些東西還蠻方便的。

OK,那Linux kernel是怎麼做的呢?就是用頭兩篇文章的方式啦~每個不同的CPU架構各自手工實作一份dump_stack()。

為啥不用GCC的機制?畢竟...嗯,我猜想,除了backtrace以外,開發者還會想看其他register的值,還有一些有的沒的,所以光是GCC提供的介面是很難印出全部所要的資訊,與其用半套GCC的機制,不如全都自己來~arm的實作大致上長這樣,可以看到基本上就只是透過迭代fp, lr, pc來完成:

352 void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk)
353 {
354         struct stackframe frame;
355         register unsigned long current_sp asm ("sp");
356 
357         pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
358 
359         if (!tsk)
360                 tsk = current;
361 
362         if (regs) {
363                 frame.fp = regs->ARM_fp;
364                 frame.sp = regs->ARM_sp;
365                 frame.lr = regs->ARM_lr;
366                 /* PC might be corrupted, use LR in that case. */
367                 frame.pc = kernel_text_address(regs->ARM_pc)
368                          ? regs->ARM_pc : regs->ARM_lr;
369         } else if (tsk == current) {
370                 frame.fp = (unsigned long)__builtin_frame_address(0);
371                 frame.sp = current_sp;
372                 frame.lr = (unsigned long)__builtin_return_address(0);
373                 frame.pc = (unsigned long)unwind_backtrace;
374         } else {
375                 /* task blocked in __switch_to */
376                 frame.fp = thread_saved_fp(tsk);
377                 frame.sp = thread_saved_sp(tsk);
378                 /*
379                  * The function calling __switch_to cannot be a leaf function
380                  * so LR is recovered from the stack.
381                  */
382                 frame.lr = 0;
383                 frame.pc = thread_saved_pc(tsk);
384         }
385 
386         while (1) {
387                 int urc;
388                 unsigned long where = frame.pc;
389 
390                 urc = unwind_frame(&frame);
391                 if (urc < 0)
392                         break;
393                 dump_backtrace_entry(where, frame.pc, frame.sp - 4);
394         }
395 }
了解原理與不同的可能作法後,對於kernel所採取的的作法就可以更容易體會嘍~ Really fun~

這個網誌中的熱門文章

kernel panic之後怎麼辦?

今天同事在處理一個陌生的模組時遇到kernel panic,Linux印出了backtrace,同事大致上可以知道是在哪個function中,但該function的長度頗長,短時間無法定位在哪個位置,在這種情況下,要如何收斂除錯範圍呢?更糟的是,由於加入printk會改變模組行為,所以printk基本上無法拿來檢查參數的值是否正常。一般這樣的問題會backtrace的資訊來著手。從這個資訊我們可以知道在function中的多少offset發生錯誤,以x86為例(從LDD3借來的例子):

Unable to handle kernel NULL pointer dereference at virtual address 00000000
printing eip:
d083a064
Oops: 0002 [#1]
SMP
CPU:    0
EIP:    0060:[<d083a064>]    Not tainted
EFLAGS: 00010246   (2.6.6)
EIP is at faulty_write+0x4/0x10 [faulty]
eax: 00000000   ebx: 00000000   ecx: 00000000   edx: 00000000
esi: cf8b2460   edi: cf8b2480   ebp: 00000005   esp: c31c5f74
ds: 007b   es: 007b   ss: 0068
Process bash (pid: 2086, threadinfo=c31c4000 task=cfa0a6c0)
Stack: c0150558 cf8b2460 080e9408 00000005 cf8b2480 00000000 cf8b2460 cf8b2460
       fffffff7 080e9408 c31c4000 c0150682 cf8b2460 080e9408 00000005 cf8b2480
       00000000 00000001 00000005 c0103f8f 00000001 080e9408 00000005 00000005
Call Trace:
 [<c0150558>] vfs_write+0xb8/0x130
 [<c0150682>] sy…

中文試譯:Writing a game in Python with Pygame. Part I

原文作者:Eli Bendersky
原文連結:http://eli.thegreenplace.net/2008/12/13/writing-a-game-in-python-with-pygame-part-i/


簡介
遊戲是最能應用程式設計技巧的領域之一。為了寫出最簡單的遊戲,你必須跟圖像、數學、物理甚至是人工智慧打交道。寫遊戲非常酷,而且也是練習程式設計的有趣方式。

如果你是Python的粉絲(就算你不是也無妨),並且對遊戲有興趣,那麼Pygame就是很屌的遊戲程式設計庫,你一定要注意它。它可以在所有主要的平台執行,並提供簡單的工具去管理複雜的、充滿變動與音效的世界。

在網路上有很多Pygame的教學,但大都太過簡單了。甚至是Pygame book都停留在入門的程度。為了達到更高的水準,我決定自己寫一套教學文件,希望可以為那些使用Pygame的朋友提供進階的學習。

這份教學鼓勵讀者去把玩程式碼,也非常建議對最後的練習題作些功課。這樣作可以讓你對這些教學有更好的瞭解。

預備知識
因為我在前面提過的理由,這份教學並不是給完全的初學者閱讀的。如果你才開始接觸Pygame,先到這個網頁裡看一些基本的教學。這份教學也很適合初學Pygame。

在這篇文章,我假設你有下列知識:

    >>Python(你不必是進階使用者,但也不能是完全的菜鳥)
    >>基本的數學與物理(向量、矩形、運動定律、機率等等)。我會解釋所有不那麼明顯的部份,但我不會教你如何對向量作加法。
    >>對Pygame有一些瞭解。你至少必須有瀏覽過在上面提到的教學裡的例子。


喔,還有一件事...這份教學主要考慮2D遊戲。3D有著另一層的困難度,我以後會提出一個自行開發一部份、簡單、不過足夠完整的3D demo。

我們開始吧!
在這個部份,我們最後會完成一個模擬 - 有著在地上爬的小生物,會蠕動,然後碰到牆壁也會反彈,並偶而改變它們的行進方向:

這當然不是一個遊戲,不過卻是一個很有用的開頭,讓我們可以實作不同的想法。我延遲給出這個遊戲最終會變成的模樣,當作給我自己的奢侈享受。

程式碼
part 1的完整程式碼以及所有的圖像,可以從這邊下載。我建議你下載它並且執行。讓程式碼在你眼前有很大的幫助。我以Python 2.5.2以及Pygame 1.8.1測試,不過其他版本應該也都能執行。

Pygam…