用 strace 找出 Ubuntu 如何提示未安裝的指令

在 Ubuntu 下執行指令後, 若沒有安裝指令的話, 會出現提示:

$ apt-rdepends
The program 'apt-rdepends' is currently not installed.  You can install it by typing:
sudo apt-get install apt-rdepends

但若直接用 bash 執行, 卻不會有這效果:

$ bash -c apt-rdepends
bash: apt-rdepends: command not found

以前覺得很好奇, Ubuntu 怎麼做到這件事的, 知道 strace 以後, 追這類原因簡單許多, 只要有輸入和輸出訊息, 就可夾擊出一些線索。

  • 在 terminal 1 輸入 echo $$ 取得該 bash 的 PID
  • 在 terminal 2 輸入 sudo strace -obash.trace -f -s512 -p PID
    • -obash.trace 表示將輸出存到 bash.trace, 訊息很多, 通常都會寫到檔案裡
    • -s512 表示輸出訊息最多到 512, 預設行寬有點短, 之後不方便找輸出的訊息
    • -f 表示一起追蹤 child process, 這點很重要, 沒加 -f 就追不到 bash 使用的其它子程序, 而關鍵就在 bash 叫起的子程序
    • -p PID 表示追蹤其它 process, 照理說同一個使用者不用 root 權限應該也能看, 不知為啥不通
  • 在 terminal 1 輸入 apt-rdepends, 因為 strace 有用 "-f", 速度會慢很多。等待指令完成
  • 在 terminal 2 按 Ctrl+C 中斷 strace, 觀察 bash.trace

搜一下 "apt-rdepends" 會看到 bash 在嘗試各種 path 後都找不到檔案:

16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/local/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/local/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/games/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...

之後在別的 child process 呼叫外部程式執行 /usr/lib/command-not-found:

16877 execve("/usr/bin/python", ["/usr/bin/python", "/usr/lib/command-not-found", "--", "apt-rdepends"], [/* 38 vars */]) = 0

若想研究怎麼找出該裝的套件, 可以研究 "/usr/lib/command-not-found"。若想研究 bash 如何判斷在有 terminal 的情況下要多找安裝的指令, 可以自己編含 debug symbol 的 bash, 再用 gdb 找出相關位置, 再讀附近的原始碼。這樣一來, 至少知道需要追的時候該如何進行, 剩下就是增加經驗提昇追程式的速度了。

2012-01-23 更新

經 wens 提醒, 原來是用 bash 的 hook 做的, 見 /etc/bash.bashrc 瞭解設定, man bash 在 COMMAND EXECUTION 那節有說明。


书籍推荐