2017年9月25日 星期一

編譯別人的程式的除錯技巧

最近玩 AI 一直在編譯別人寫的軟體。 最常遇到的問題, 不是別人寫的程式有錯, 而是我的環境跟他的環境沒有 100% 相同, 所以原來 ok 的程式碼, 到了我的環境就編譯失敗。 以下是我 編譯 ENet 時學到的一些除錯技巧與心得。

如果你想要編譯的軟體是很熱門的軟體, 說不定已經有人打包好 docker 了 -- 搜尋一下, 直接抓回來用比較快。 或是沒有建好的 docker, 只有 Dockerfile 可看, 一步一步照著做也比自己在黑暗中摸索要輕鬆很多。

如果沒有, 只好自己編譯。 開始編譯之前, 可能要先按照指示安裝一堆套件。 例如 ENet 用到修改版的 caffe, 所以要先編譯 caffe。 再次地, 大致照著 caffe 的 Dockerfile 的步驟, 參考 編譯文件 做, 可能會比只讀文件簡單很多。 如果可以搜尋得到已包好相依套件的現成 docker, 也可省下很多工。 例如我拿 ckhung/cvbstnpy 這個 docker 當基底, 所以可以省略 (很難編譯的) opencv、 boost 等等套件。

說編譯其實並不精確。 從設定檔、 原始碼到最後執行檔的完整流程, 稱為 build。 Build 通常包含 configure、 compile、 link 三個階段。 還有不少 AI 程式用到 cmake, 但我很不熟悉。

Compile 過程常會遇到找不到 include 檔的錯誤。 從 這裡 學到: 先用 find / -iname 'xyz.h' 找到你的 header file, 再用 make SHELL='sh -x' 重新執行一次。 這會把每個指令完整展開, 以便查看 gcc 或 g++ 到底去哪些目錄尋找 header files。 從 gcc/g++ 命令列上的諸多 -I 選項當中找出一個跟實際路徑長得最像的, 再來修改。 例如我在 make pycaffe SHELL='sh -x' 的時候, 發現 g++ 找不到 numpy/arrayobject.h 。 用 find / -iname arrayobject.h 找到它真實的位置是在 /usr/local/lib/python3.5/site-packages/numpy/core/include/numpy/arrayobject.h 。 而 g++ 的命令列上則發現了一個長得很像的 -I/usr/local/lib/python3.5/dist-packages/numpy/core/include 。 所以最簡單的解法就是: cd /usr/local/lib/python3.5 ; ln -s site-packages dist-packages

我第一次編譯時, 為了想知道 gcc/g++ 到底從哪些目錄 include, 還搜尋過 1 2 3 等等問答; 不過最好用的應該還是 make SHELL='sh -x' 這招。

這招也適用於 link 階段。 比方說 g++ 抱怨找不到 boost_numpy3 函式庫, 就用 find / -iname '*boost_numpy3*.so*' 查出函式庫實際的位置 (因為全名可能叫做 libboost_numpy3.so.1.64.0 之類的), 再跟 make SHELL='sh -x' 印出的 g++ 命令列上所有 -L 路徑比對。

如果原始碼是 c++ 程式, link 時還可能會遇到 g++ name mangling 的問題: g++ (其實是 ld) 抱怨找不到某個函數或變數, 但那個名字看起來像亂碼。 這時可以用 c++filt 把它轉換回原始碼有意義的函數名稱, 類似這樣: echo _ZNKSt13runtime_error4whatEv | c++filt 得知它其實是這個函數: std::runtime_error::what() const 這樣才能繼續 google 追問題。

我從原作者那邊 clone 出一份 ENet, 修改的地方很少, 主要加上一個修改過的 caffe-enet 的 Makefile.config , 及加上一個 Dockerfile, 可當作本文的參考練習範例。 建好的 docker 在 ckhung/enet, 可以拿來做 街道圖片 semantic segmentation 語義分割

1 則留言:

  1. 雖然只看得懂一點點,不過就是在講解決問題的方法。
    Linux 下常常要解覺問題,這種解決問題的方法的文章很有價值,即使不一定每個人都用的到。

    回覆刪除