2017年6月18日 星期日

Dynamical Variable Assignment in Makefile

上星期遇到奇怪問題卡了我一天:第一次 make 成功,直接重新 make 第二次結果失敗了,第三次 make 又成功?為什麼 make 結果會不一樣?

一開始找資料發現 Makefile 針對變數指定方式的不同,會有兩種展開方式:

Two Flavors of Variables

Recursively expanded variable v.s. Simply expanded variable
一種是使用=,之後執行時都會回來改變等號左邊的變數(recursively),而另一種使用:=,將當下的值指定給等號左邊的變數(simply),之後不再更動。
Recursively 範例:
x = foo
y = $(x) bar
x = later
echo $(y) 的輸出結果會是 later bar
Simply 範例:
x := foo
y := $(x) bar
x := later
echo $(y) 的輸出結果是 foo bar
認知升級後,馬上將 Makefile 的等號改成 :=,結果根本不是這個原因…只好繼續找資料,深追問題。
後來發現問題是在 rule 中使用 $(shell ... ) 動態指派變數的話,rule 在執行前就會先展開 $(shell ... ) 了,意思是 $(shell ...) 比 rule 還早被執行。

使用 $eval(…) 搭配 $(shell …) 動態執行指令

以下透過 Makefile 範例來解釋:
執行 make 後會先砍掉所有 rom 檔,並建立 test.rom,確保當前目錄只有 test.rom,接著透過 find 指令查找 rom 檔,將檔名 assign 給變數 ROM
all:
    @rm -rf *.rom
    @touch test.rom
    $(eval ROM := $(shell find ./ -name *.rom))
    @echo name=$(ROM)
情境一、當前目錄不存在 *.rom 時:
[user][linux][~/test]% ls *.rom
ls: No match.
[user][linux][~/test]% make all
name=
情境二、當前目錄存在 *.rom 時:
[user][linux][~/test]% touch existed.rom
[user][linux][~/test]% ls *.rom
existed.rom
[user][linux][~/test]% make all
name=./existed.rom
造成上面兩個結果不同的原因,因為 $(shell ...)先執行了!有注意到情境二的 name 是 existed.rom 而非 Makefile 中建立的 test.rom 嗎?

改用 Backquote 如何?

想說好吧,不呼叫 shell 了,用另一種方式解看看。於是使用 backquote (或稱 backtick) ` 執行 find 指令,例如:
all:
    @rm -rf *.rom
    @touch test.rom
    $(eval ROM := `find ./ -name *.rom`)
    echo "before mv: name=$(ROM)"
    mv $(ROM) newname.rom
    echo "after mv: name=$(ROM)"
輸出結果:
[user][linux][~/test]% make all
echo "before mv: name=`find ./ -name *.rom`"
before mv: name=./test.rom
mv `find ./ -name *.rom` newname.rom
echo "after mv: name=`find ./ -name *.rom`"
after mv: name=./newname.rom
的確,使用 backquote 變成當下才去執行指令,符合我的預期。但其實我是想把 find 的輸出結果當成 constant string 來使用,而實際上 $(ROM) 存的卻是完整的指令,不是指令執行完的輸出結果…
所以最後,我放棄在 Makefile 動態指派變數的值了,把想要執行的指令直接寫成 script 讓 Makefile 呼叫,省去不必要的麻煩。

Reference

  1. The Two Flavors of Variables
    https://ftp.gnu.org/old-gnu/Manuals/make-3.79.1/html_chapter/make_6.html#SEC59
  2. Stackoverflow
    https://stackoverflow.com/questions/34935544/how-to-time-the-execution-of-a-gnu-make-rule

2017年5月30日 星期二

[閱讀] 《人魚沉睡的家》原來腦死和植物人不一樣?

《人魚沉睡的家》
這本小說是由我最喜歡的推理小說作家東野圭吾所寫,近三年才開始接觸東野圭吾的小說,喜歡他的小說中引導讀者思考的情節對話。小說中常探討一些價值觀,這些價值觀不一定有對錯,僅是因為站在不同的立場,不同的成長背景,不同的經歷,因此思考出來的結果以及選擇與人不同罷了。身為使用上帝視角來看故事的讀者,我們很幸運地可以試者去思考、去同理書中每位角色的心情與想法,或許這是身為讀者最幸福的事情了。
書名的人魚是用來描述一名年幼可愛的女孩因為發生了意外,沒有意識,躺在床上或輪椅上安詳的模樣。
讀了這本小說才認識到原來植物人與腦死是不一樣的,以下綜合小說與網路查詢資料,整理出兩者的差別。

植物人 v.s. 腦死

植物人是指大腦死亡但腦幹沒死,腦死是指腦幹死亡。
植物人由於腦幹機能還正常,保有以下特性:
  • 生理時鐘在轉,清醒(awake)但沒有意識(awareness),眼睛張開
  • 無法與他人互動
  • 無意識行為
  • 自發性呼吸(腦幹正常)
  • 非自發性運動(還是能進食、排泄)
腦死因為腦幹機能喪失,腦幹無法發揮反射的功能,瞳孔對光沒有反應,對痛覺沒有反應,也無法自主呼吸,需仰賴人工呼吸器,只要關閉人工呼吸器便會死亡。
腦死判定(即腦幹反射測試)︰瞳孔對光反射消失、眼球不會轉動、對疼痛沒有任何反應、刺激支氣管時,沒有作嘔或咳嗽反射等。
為什麼要區分是不是腦死?因為現行法規下定義,腦死即代表死亡,一定要判定腦死,醫生才可以摘取器官進行器官捐贈、移植。

延伸思考

  • 植物人有沒有可能醒過來?
  • 腦死有沒有可能醒過來?
  • 你是否接受器官捐贈?
  • 家人是否接受器官捐贈?

延伸閱讀

  1. 腦死(Brain death)
    https://smallcollation.blogspot.tw/2013/05/brain-death.html#gsc.tab=0
  2. 植物人有沒有意識?
    https://scitechvista.nat.gov.tw/c/7LqS.htm

2017年4月26日 星期三

Blog CSS Test

Blog CSS Test

H1 Text

H2 Text

1#

2#

3#

4#

5#

5 is the normal size

6#

↓分隔線↓ 中文字型測試


Normal text
Bold text
Italic text

text block 1

text block 2

This is text block 3...test with large widthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
and the second line
int main(void)
{
    printf("This is code block...test with large widthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
    return 0;
}

Finally, test with block quote
Written with StackEdit.

2017年4月23日 星期日

Linux Kernel Panic Analysis

前言


這篇文章主要分享在 CentOS 下,使用 crash 分析 vmcore 除錯的經驗。
上星期收到一件來自日本的客服案例:客戶的 Linux server 發生 panic 導致系統重啟。客戶將 kernel panic 產生的 vmcore 以及 server vendor – Red Hat 的反饋一起傳給我們,由於 Red Hat 反饋表示這是我們 driver 的問題,於是客戶就要求我們針對 7GB 的 vmcore 進行分析。
其實我沒有處理過 Linux kernel panic 的經驗(只有接過 Windows BSOD1 的案子),問了公司幾位前輩,通常發生這種問題時,客戶只會給我們純文字 error log 以及問題的複現流程。但這次客戶只發生過一次 panic,也不知道如何複製問題,我唯一的資訊只有 vmcore,因此我才花了一些時間學習如何分析 vmcore。

工具準備及環境建置


這次分析需要安裝:
  • crash
  • vmlinux
首先安裝 crash 這套工具,直接安裝很容易:
yum install crash
接下來輪到 vmlinux,需安裝與 vmcore 相對應的 kernel 版本才行,否則會遇到 vmlinux and vmcore do not match! 的錯誤。
要怎麼知道 vmcore 是在哪一版的 kernel 產生的呢?可以用 strings 去抓 vmcore 的資訊:
[root@localhost ~]# strings vmcore | grep OSRELEASE
OSRELEASE=3.10.0-327.el7.x86_64
^C
[root@localhost ~]# 
現在得知OSRELEASE=3.10.0-327.el7.x86_64,就可以到 debuginfo.centos.org 下載對應版本的 kernel-debuginfo 了,其中 vmlinux 就包含在 debuginfo package 裡面。
由於 debuginfo 依存於 debuginfo-common ,因此這兩項都要下載並透過 rpm 安裝:
[root@localhost ~]# rpm -ivh kernel-debuginfo-common-x86_64-3.10.0-327.el7.x86_64.rpm
[root@localhost ~]# rpm -ivh kernel-debuginfo-3.10.0-327.el7.x86_64.rpm
安裝完畢,在以下目錄應該能找到所需的 vmlinux:
[root@localhost ~]# find /usr/lib/debug/lib/modules/3.10.0-327.el7.x86_64/ -name vmlinux
/usr/lib/debug/lib/modules/3.10.0-327.el7.x86_64/vmlinux
[root@localhost ~]#

開始分析


執行 crash 並帶入 vmlinux 與 vmcore,會看到類似以下畫面:
[root@localhost ~]# crash /usr/lib/debug/lib/modules/3.10.0-327.el7.x86_64/vmlinux vmcore 

crash 7.1.5-2.el7
Copyright (C) 2002-2016  Red Hat, Inc.

...(略)

  KERNEL: /usr/lib/debug/lib/modules/3.10.0-327.el7.x86_64/vmlinux
DUMPFILE: vmcore
    CPUS: 8
    DATE: Mon Apr  3 04:16:14 2017
  UPTIME: 47 days, 02:41:16
LOAD AVERAGE: 0.74, 0.33, 0.18
   TASKS: 676
NODENAME: xxx
 RELEASE: 3.10.0-327.el7.x86_64
 VERSION: #1 SMP Thu Oct 29 17:29:29 EDT 2015
 MACHINE: x86_64  (2594 Mhz)
  MEMORY: 31.7 GB
   PANIC: "Kernel panic - not syncing: Watchdog detected hard LOCKUP on cpu 5"
     PID: 0
 COMMAND: "swapper/5"
    TASK: ffff8808541f5c00  (1 of 8)  [THREAD_INFO: ffff880852b98000]
     CPU: 5
   STATE: TASK_RUNNING (PANIC)
很好,有種進入新世界的喜悅,也獲得新的資訊 Kernel panic - not syncing: Watchdog detected hard LOCKUP on cpu 5 。有興趣可以 google 一下 soft/hard LOCKUP 的差別。
crash 內建了許多好用的指令,首先 bt 可以說是必用的指令之一。

bt - backtrace

crash> bt
PID: 0      TASK: ffff8808541f5c00  CPU: 5   COMMAND: "swapper/5"
 #0 [ffff88087fd459c8] machine_kexec at ffffffff81051beb
 #1 [ffff88087fd45a28] crash_kexec at ffffffff810f2542
 #2 [ffff88087fd45af8] panic at ffffffff8162ea73
 #3 [ffff88087fd45b78] watchdog_overflow_callback at ffffffff8111b512
 #4 [ffff88087fd45b88] __perf_event_overflow at ffffffff8115ed71
 #5 [ffff88087fd45c00] perf_event_overflow at ffffffff8115f844
 #6 [ffff88087fd45c10] intel_pmu_handle_irq at ffffffff810325a8
 #7 [ffff88087fd45e60] perf_event_nmi_handler at ffffffff8163ebeb
 #8 [ffff88087fd45e80] nmi_handle at ffffffff8163e339
 #9 [ffff88087fd45ec8] do_nmi at ffffffff8163e4e9
#10 [ffff88087fd45ef0] end_repeat_nmi at ffffffff8163d771
    [exception RIP: ioread8+66]
    RIP: ffffffff81309532  RSP: ffff88087fd43e28  RFLAGS: 00000092
    RAX: ffffc90013422a7e  RBX: 0000000000000020  RCX: 0000000000000000
    RDX: 0000000000009580  RSI: ffff88085f408a00  RDI: ffffc90013422a06
    RBP: ffff88087fd43ea8   R8: 0000000000000000   R9: 0000000000000082
    R10: ffff88085f403500  R11: 000e783a5cdff909  R12: 0000000000000022
    R13: 000000000000000e  R14: 0000000000000f50  R15: ffffffffa03fac10
    ORIG_RAX: ffffffffffffffff  CS: 0010  SS: 0018
--- <NMI exception stack> ---
#11 [ffff88087fd43e28] ioread8 at ffffffff81309532
#12 [ffff88087fd43e28] mx_interrupt at ffffffffa03f1940 [mxupcie]
#13 [ffff88087fd43eb0] handle_irq_event_percpu at ffffffff8111c2be
#14 [ffff88087fd43ef8] handle_irq_event at ffffffff8111c49d
#15 [ffff88087fd43f20] handle_fasteoi_irq at ffffffff8111f90a
#16 [ffff88087fd43f40] handle_irq at ffffffff81016ecf
#17 [ffff88087fd43f78] do_IRQ at ffffffff81647daf
--- <IRQ stack> ---
...(略)
可以看到發生問題的點在 [exception RIP: ioread8+66],其中 RIP 是一個 instruction pointer 的暫存器,它指向了正在被執行的指令的記憶體位置。更重要的是,我發現了在 call trace #12 顯示的 mx_interrupt at ffffffffa03f1940 [mxupcie] 正好是我們 driver 的一個函式。
獲得以上訊息,接著我們可以利用 dis 命令將其反組譯:

dis - disassembling instruction

dis 後面接 address 或 symbol,加個 -l 參數可以直接指出 source code line number。
crash> dis -l ioread8+66
/usr/src/debug/kernel-3.10.0-327.el7/linux-3.10.0-327.el7.x86_64/lib/iomap.c: 73
0xffffffff81309532 <ioread8+66>:        movzbl %al,%eax
crash> 
crash> dis -l ffffffffa03f1940
0xffffffffa03f1940 <mx_interrupt+384>:  test   $0xf,%al
crash> 
注意到了嗎? 反組譯 ioread8+66 可以指出位於 iomap.c: 73,但是反組譯 mx_interrupt 所在位置 ffffffffa03f1940 卻不知道是哪一行程式。其實原因是沒有載入 driver module 的 symbol,所以認不出來,因此只要利用 mod 指令載入 module 2就沒問題囉!
crash> mod -s mxupcie /mydriver/mxupcie.ko
     MODULE       NAME                    SIZE  OBJECT FILE
ffffffffa03f9120  mxupcie                69583  /mydriver/mxupcie.ko 
crash> dis -l ffffffffa03f1940
/mydriver/mxpcie.c: 2485
0xffffffffa03f1940 <mx_interrupt+384>:  test   $0xf,%al
crash> 
最後得到 mxpcie.c: 2485 ,恭喜你終於可以開始對程式碼 debug 啦~!

註釋


  1. Blue Screen of Death (BSOD) - Windows 當機,顯示藍屏畫面。Windows BSOD 預設會產生 minidump,R&D 可以透過 minidump 查詢當機的原因,個人覺得與本案例 Linux 的 vmcore 類似。有需要也可以修改 Windows 系統預設值,使當機時產生full dump,而非只有部分核心訊息的 minidump。
  2. 載入 module symbol 時,有意識到客戶用 Red Hat build 出來的 driver 和我在 CentOS build 出來的可能會有差異,雖然都是 Kernel 3.10,但不確定有無影響。因此我使用了 objdump 反組譯 CentOS build 出來的組合語言,比對 test $0xf,%al 這一行是否指到同一行程式碼,還好結果是相同的。