顯示具有 gdb 標籤的文章。 顯示所有文章
顯示具有 gdb 標籤的文章。 顯示所有文章

2017年9月3日 星期日

讀出 gcc/g++ 編譯的參數

從這裡看來的,官方文件說 -grecord-gcc-switches 預設有開,所以滿可靠的。

使用例子:

$ g++ f.cpp -std=c++11 -g -o f
$ readelf --debug-dump f | grep DW_AT_producer
       DW_AT_producer    : (indirect string, offset: 0xded): \
  GNU C++11 5.4.0 20160609 -mtune=generic -march=x86-64 -g -std=c++11 -fstack-protector-strong
    DW_AT_producer     DW_FORM_strp

但用 clang 編的資訊就沒存完整:

$ clang++ f.cpp -std=c++11 -g -o f
$ readelf --debug-dump f | grep DW_AT_producer
       DW_AT_producer    : \
  (indirect string, offset: 0x0): clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
    DW_AT_producer     DW_FORM_strp

2014年2月9日 星期日

從 third-party library 的 console 錯誤訊息找出更多線索

最近遇到一個不錯的小例子, 可以比較完整地記錄用到的相關指令。更多關於 gdb 的說明, 見 gdb 初步心得。

問題

Ubuntu 下執行程式後看到 "Gtk-WARNING **: cannot open display:", 然後程式就掛了。

解決過程

為了獲取更多線索, 我想找出是那段程式輸出這個錯誤訊息。由 "Gtk-WARNING" 可知是 Gtk+ 函式庫輸出的錯誤訊息。所以得先取得 Gtk+ 的 debug symbol 和原始碼:

$ dpkg -l | grep gtk
...
ii  libgtk2.0-0
...

找出已安裝的 Gtk+ 函式庫叫做 libgtk2.0, 接著找出它對應的 debug symbol package 和原始碼:

$ aptitude search libgtk2.0
i   libgtk2.0-0             - GTK+ graphical user interface library
...
i   libgtk2.0-0-dbg         - GTK+ libraries and debugging symbols
...
i   libgtk2.0-dev           - development files for the GTK+ library
...

然後安裝 debug symbol, 還有取得 Gtk+ 的原始碼:

$ aptitude install libgtk2.0-0-dbg
$ apt-get source libgtk2.0-dev

從取得的原始碼找看看有沒有 "cannot open display":

$ cd gtk+2.0-2.24.10/
$ grep "cannot open display" -R .
./gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./gdk/gdk.c:      g_warning ("cannot open display: %s", display_name ? display_name : "");
./.pc/071_no_offscreen_widgets_grabbing.patch/gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./.pc/100_overlay_scrollbar_loading.patch/gtk/gtkmain.c:      g_warning ("cannot open display: %s", display_name_arg ? display_name_arg : "");
./ChangeLog.pre-2-12:   * gtk/gtkmain.c: (gtk_init): Fix "cannot open display" error message

讀一下字串出現相關的位置, 找出函式 gtk_init_check 和 gdk_init_check。看來沒有找錯方向, 於是可以用 cgdb 執行程式, 進入 gdb 後輸入:

(gdb) b gtk_init_check
(gdb) b gdk_init_check
(gdb) directory /my/path/to/gtk+2.0-2.24.10/
(gdb) set substitute-path /.../gtk+2.0-2.24.10/ /my/path/to/gtk+2.0-2.24.10/
(gdb) r
  • directory 用來載入 Gtk+ 原始碼。debug symbol package 裡沒有含這部份。
  • 編譯封裝 Gtk+ 的路徑十之八九和我放 Gtk+ 原始碼的路徑不同, 所以要用 set substitute-path 替換路徑, gdb 才知道如何顯示對應的原始碼。上面的 "..." 是執行 backtrace 時看到的路徑, 這裡簡化用 "..." 表示。

再來就在中斷點附近輸出收到的值, 看看 backtrace, 看看附近的原始碼, 獲得更多線索後就解決問題了。原來是我忘了呼叫 setenv("DISPLAY", ":0"), gdk_init_check() 發現無法產生 GdkDisplay, 所以就掛了。

2013年12月29日 星期日

在 Ubuntu 下用 gdb debug CPython

用法和其它 library 稍微不同, 安裝 python-dbg 後, 是提供另一個含有 debug symbol 的 python 執行檔 python2.7-dbg, 而不是 gdb 自己另外載入 python 的 debug symbol。

可從 dpkg -L python-dbg 的結果得知這事:

$ dpkg -L python2.7-dbg | grep bin/python
/usr/lib/debug/usr/bin/python2.7-gdb.py
/usr/lib/debug/usr/bin/python2.7
/usr/bin/python2.7-dbg
/usr/bin/python2.7-dbg-config
/usr/lib/debug/usr/bin/python2.7-dbg-gdb.py
$ nm /usr/bin/python2.7
nm: /usr/bin/python2.7: no symbols
$ nm /usr/bin/python2.7-dbg | head
                 U ASN1_INTEGER_get@@OPENSSL_1.0.0
                 U ASN1_STRING_data@@OPENSSL_1.0.0
                 U ASN1_STRING_length@@OPENSSL_1.0.0
                 U ASN1_STRING_to_UTF8@@OPENSSL_1.0.0
                 U ASN1_TIME_print@@OPENSSL_1.0.0
                 U ASN1_item_d2i@@OPENSSL_1.0.0
00000000008857e0 d AST_type
0000000000967588 b Add_singleton
00000000009675e8 b Add_type
0000000000967560 b And_singleton

實際執行的結果:

$ gdb --args python2.7-dbg -c 'import time; time.sleep(10)'
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
Reading symbols from /usr/bin/python2.7-dbg...done.
(gdb) r
Starting program: /usr/bin/python2.7-dbg -c import\ time\;\ time.sleep\(10\)
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
^C
Program received signal SIGINT, Interrupt.
0x00007ffff69ad003 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0   __select_nocancel at ../sysdeps/unix/syscall-template.S:82
#1   floatsleep at ../Modules/timemodule.c:943
#2   time_sleep at ../Modules/timemodule.c:206
#3   PyCFunction_Call at ../Objects/methodobject.c:81
#4   call_function at ../Python/ceval.c:4021
#5   PyEval_EvalFrameEx at ../Python/ceval.c:2666
#6   PyEval_EvalCodeEx at ../Python/ceval.c:3253
#7   PyEval_EvalCode at ../Python/ceval.c:667
#8   run_mod at ../Python/pythonrun.c:1353
#9   PyRun_StringFlags at ../Python/pythonrun.c:1316
#10  PyRun_SimpleStringFlags at ../Python/pythonrun.c:969
#11  Py_Main at ../Modules/main.c:583
#12  main at ../Modules/python.c:23

順便備忘一下相關的文章: Debugging C/C++ and CPython using GDB 7′s new Python extension support 提到如何在 gdb 內使用 python extension 來 debug python code, 可以從 gdb 內看到 C 函式對回去 python script 的檔名和行數。

2013年11月18日 星期一

gdb "backtrace full" 不會停的可能原因

當程式有狀況的時候, 可以用下面的指令取得所有 thread 的 backtrace 含帶區域變數的資訊:

gdb --batch --quiet -ex 'thread apply all backtrace full' -p PID

但是以前遇過幾次下完指令後不會結束的狀況, 不知道確切原因為何。最近剛好遇到一個容易重製的情境, 總算找到一種可能的原因。答案是: 有區域變數使用 reference 指向 dangle pointer, 於是 gdb 讀不出區域變數的資訊, 變成輸出到這個 reference 的時候, 持續輸出一堆 "Cannot access memory ..." 而不會結束。

若區域變數是指標, gdb 只會輸出指標的值。但 reference 等同於一般變數 (不知「非指標非參考」的正式名稱是什麼), gdb 會輸出整個變數 (物件) 的值, 於是 reference 指向 dangle pointer 就成了糟糕的組合。

我遇到的情況 dangle pointer 剛好含有 vector, 可能因此找不到 vector 的結束點而不會停止。Btw, 原本想說試看看用 python gdb 看看能不能自己寫個 backtrace 不會卡住, 可惜用 python gdb 一樣會卡住, 沒有丟出 exception。

2013年8月24日 星期六

用 gdb 略過部份程式碼

尋找「誰是兇手」的時候, 以往的作法是註解掉部份程式、重新編譯、重新執行、觀察結果是否有變化。然後反覆前述動作直到找到兇手為止。編譯的時間略長的時候, 這個過程頗為痛苦。

幸好 gdb 有跳掉部份程式碼的方法。其中一個是用 return 結束目前所在的函式, 返回上一層 frame。另一個作法是先設中斷點, 再用 jump 跳到中斷點。

用 return 的小小缺點是: gdb 每次都會問你 yes/no, 用 jump 則無此困擾。可以用 define 寫個簡單的函式, 設暫存中斷點, 再用 jump 跳到中斷點, 藉此忽略中間的程式碼。

作法如下:

# skip the next N line statements
define j
  if $argc == 1 && $arg0 > 0
    tbreak +$arg0
    jump +$arg0
  end
end

之後就可以用 j N 跳掉接下來的 N 行程式。

2013年8月15日 星期四

用 python gdb 客製化 backtrace 的結果 (2)

幫之前寫的指令 bt 加上參數 -s, 使用 -s 時, 會一併記錄 backtrace 每個 frame 附近的原始碼。

以下是一個範例輸出:

(gdb) bt -s
#0   A::hello at b.cpp:8
  | class A
  | {
  | public:
  |   void hello(int n)
  |   {
->|     std::cout << n << std::endl;
  |   }
  |
  |   void foo(int n)
  |   {

#1   A::bar at b.cpp:18
  |     bar(n + 1);
  |   }
  |
  |   void bar(int n)
  |   {
->|     hello(n + 10);
  |   }
  | };
  |
  | int main(void) {

#2   A::foo at b.cpp:13
  |     std::cout << n << std::endl;
  |   }
  |
  |   void foo(int n)
  |   {
->|     bar(n + 1);
  |   }
  |
  |   void bar(int n)
  |   {

#3   main at b.cpp:24
  |   }
  | };
  |
  | int main(void) {
  |   A a;
->|   a.foo(5);
  |   return 0;
  | }

修改後的 python script 如下:

作法大致和 《用 python gdb 客製化 backtrace 的結果》一樣, 只是改取檔案的 fullname, 再自己讀出相關的原始碼。這部份用 python 實作, 相當地輕鬆寫意。

2013年8月3日 星期六

用 python gdb 客製化 backtrace 的結果

需求

想要了解模組之間函式呼叫的關係時, 與其一層層比對多個類別之間的呼叫關係, 不如直接在最後一個呼叫函式放中斷點, 直接顯示 backtrace。但是當函式裡有太多參數或 template 時, backtrace 的 frame 訊息會變得很長, 不易閱讀。我的目的只是找出呼叫的函式名稱、檔名和行數, 函式帶的參數反而是困擾。

作法一: 用 gdb.execute()

一個簡單的作法是截取 gdb 的輸出, 然後解析文字去掉不要的部份:

Btw, 上面的作法還順便幫行首的標號上色。

但是, 使用 cgdb 時會無法運作, 理由是 cgdb 使用 GDB MI, gdb.execute('backtrace') 的結果不是原本看到的格式, 難以解析。

作法二: 用 gdb.Frame() API

只好改用中規中矩的方式逐一讀取 frame, 取出需要的資訊:

將上面的 script 存到 /path/to/gdb/scripts/backtrace.py, 接著在 $HOME/.gdbinit 裡加入以下設定:

python
sys.path.insert(0, '/path/to/gdb/scripts')
import backtrace
end

之後就能用 bt 顯示精簡後的 backtrace 了, 也方便手動複製貼上到筆記裡。以下是一個輸出例子:

(gdb) bt
# 0  A::hello at a.cpp:8
# 1  A::bar at a.cpp:13
# 2  A::foo at a.cpp:18
# 3  main at a.cpp:25

Btw, 若是需求比較簡單, 可以試看看 Print Settings, 有些選項可以改變 backtrace 顯示的訊息。

參考資料:

2013年7月20日 星期六

gdb 顯示 STL container 的方法

問題描述

如下列的程式碼:

std::vector<int> numbers;
for (int i = 0; i < 4; i++) {
  numbers.push_back(i * 3);
}

std::map<std::string, std::string> contacts;
contacts["john"] = "0987-654321";
contacts["mary"] = "";

在 gdb 中不易閱讀 numbers 和 contacts 的顯示結果:

(gdb) p numbers
$1 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, _M_start = 0x6060
10, _M_finish = 0x606020, _M_end_of_storage = 0x606020}}, <No data fields>}
(gdb) p contacts
$2 = {_M_t = {_M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::basic_string<char, std::char_
traits<char>, std::allocator<char> > > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std
::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<No data fields>}, <No data fields>}, _M_key_compare = {<std::binary_function<std::basic_string<char, std
::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool>> = {<No data fields>}, <No data fields>}, _M_header = {_M_co
lor = std::_S_red, _M_parent = 0x606080, _M_left = 0x606080, _M_right = 0x606120}, _M_node_count = 2}}}

解法一: 土法煉鋼

參考這篇的作法, 先取得目標位址再取值:

(gdb) p numbers
$5 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, _M_start = 0x60601
0, _M_finish = 0x606020, _M_end_of_storage = 0x606020}}, <No data fields>}
(gdb) ptype numbers._M_impl
type = struct std::_Vector_base<int, std::allocator<int> >::_Vector_impl
    : public std::allocator<int> {
    std::allocator<int>::pointer _M_start;
    std::allocator<int>::pointer _M_finish;
    std::allocator<int>::pointer _M_end_of_storage;
  public:
    void _Vector_impl(void);
    void _Vector_impl(
    const std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Tp_alloc_type &);
}
(gdb) ptype numbers._M_impl._M_start
type = int *
(gdb) p numbers._M_impl._M_start[1]
$7 = 3
(gdb) p *(numbers._M_impl._M_start)@numbers.size()
$12 = {0, 3, 6, 9}

先用 ptype 逐步了解各欄位的型別, 繼而找到儲存資料的欄位 numbers._M_impl._M_start。 由於 _M_start 是 int*, 倒數第二個指令直接取出 numbers[1] 的值, 最後一個指令則是用 print P@N 印出從位置 P 開始 N 筆資料。

嫌麻煩的話, 可以定義新的 gdb command:

(gdb) define pv
Type commands for definition of "pv".
End with a line saying just "end".
>if $argc == 1
 >p *($arg0._M_impl._M_start)@$arg0.size()
 >end
>if $argc == 2
 >p $arg0._M_impl._M_start[$arg1]
 >end
>end
(gdb) pv numbers
$21 = {0, 3, 6, 9}
(gdb) pv numbers 1
$22 = 3

將上述的 define pv ... end 寫入 /.gdbinit, 之後就不用重寫一次。

解法二: 使用別人寫好的 pretty-printers

土法煉鋼的目的是讓我們有辦法應付日後各種需求, 但是針對 STL 這種大家都有的需求, 已經有善心人士提供完整的套件了, 見 STLSupport - GDB Wiki 的第一點。

摘要作法如下:

$ cd /path/to/gdb_script/
$ svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python
$ cat <<EOF >> ~/.gdbinit
python
import sys
sys.path.insert(0, '/path/to/gdb_script/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
EOF

載入 pretty-printers 後, 輸出變數的結果如下:

(gdb) p numbers
$1 = std::vector of length 4, capacity 4 = {0, 3, 6, 9}
(gdb) p contacts
$2 = std::map with 2 elements = {["john"] = "0987-654321", ["mary"] = ""}

有需要的時候, 也可以參考 python/libstdcxx/v6/printers.py 學習如何用 python 寫 gdb script。

2013年7月18日 星期四

加速 gdb 載入 symbol 時間

gdb 7.4 以後支援 index, 可以加速載入 gdb symbol 的時間, 參考 Index Files - Debugging with GDB 的說明, 寫了個 script 接受一個 binary 檔, 會作以下的事:

  1. 用 gdb 載入 binary 的 symbol, 並存下 symbol index 到暫存檔
  2. 加入 symbol index 到原 binary

symbol 太多時, 用 gdb index 可大幅減少載入時間, 不過每次重編又要重新產生 index 檔, 不知有沒有更好的方法, 可以在 linking 時直接在 binary 內加入 symbol index, 並且不會增加太多 linking 時間。

2013-07-19 更新

依 Viller Hsiao 留言裡的建議, 試了 gold 的 --gdb-index。這功能還滿新的, Ubuntu 12.04 的 gold 沒這選項, 不過 12.10 的 gold 有。可以用 objdump -h BINARY | grep index 確認 link 時加上 --gdb-index 確實有效。

2013-07-23 更新

用 gold 編譯加上 --gdb-index 後踩到雷, 在目前的專案裡 100% 會在執行 backtrace 後卡住不動, 接著一直狂吃記憶體, 漲到 4G 以後顯示gdb virtual memory exhausted can't allocate 4064 bytes, 然後和使用者說 gdb 掛了, 你願意結束這個 debug session 嗎?

交叉比對了下述三種情況:

  • 用 gold link (不含 --gdb-index)
  • 用 gold link (不含 --gdb-index), 事後補 gdb index
  • 用 gold link + --gdb-index
結果只有第三種會中獎, 所以現在改回用第二種方法了。

2013年6月23日 星期日

除錯小技巧: 在程式中直接中斷及偵測是否被 gdb 監控中

在程式中直接中斷

依《Binary Hack》 #92 所言, 在 gdb 監控 process 的情況, 使用下列程式碼會進入中斷:

raise(SIGTRAP);

對 x86 環境, 下列作法也許會更好, 少了一層 function call

__asm__ __volatile__("int3");

Scott 有個 debugbreak 有處理 x86 以外的情況, 有需要時再來參考看看。

偵測是否被 gdb 監控中

比起在程式中直接中斷, 我比較需要知道目前程式是否被 gdb 監控 (attach) 中。以下是兩個實用的情境:

  • 原本已有 log 錯誤訊息, 在 log 極為嚴重錯誤訊息 (如 severity = FATAL) 時, 檢查看看是否有 gdb 在監控, 有的話不如順便進入中斷, 提前察看錯誤點。
  • 寫 multi-thread 時, 有時會有特定 thread 負責特定的事, 像是 UI 或 IO。有時會監控這個 thread 是否正常運作。或是有網路連線時, 有時會想監控另一端連線是否仍然正常運作。其中一種監控 thread 或網路連線的方式是週期性察看有無更新資料, 超過一定時間沒更新, 就中止程序或關掉連線重連。在使用 gdb 除錯時, 有可能在中斷點待太久而觸發前述安全防護機制, 反而無法長時間除錯。這時自動關閉防護措施會比較方便。

《Binary Hack》 #92 的作法利用設 gdb 會覆寫 SIGTRAP 的 signal handler 做檢查, 這個作法可以跨比較多平台, 但可用時機太窄。我偏好 Chromium 的作法, 直接讀 /proc/PID/status, 由 "TracerPid" 的值得知目前是否有 process 控控自己。程式執行中的任何時間都可用這方法得知正確的結果, 不過只適用於 Linux 和 Android。以下是參考 Chromium 實作稍作修改的版本:

2012年11月28日 星期三

在 x86-64 上對 system call 使用 conditional break

在了解目標模組的動態行為時, 最近覺得從模組的 public interface 設中斷點是滿不錯的作法。廣意來說, system call 則是最一般化的 public interface, 從 system call 回頭夾擊目標也滿有效的。比方像這篇用 strace 找到切入點。

若想獲得更多資訊, 可以用 gdb 在 system call 上設中斷點, 一步步從退回來找到呼叫者、傳遞的資料等更多訊息。但在尋找 write、send 這類廣為使用的 system call 時, 被呼叫的次數過於頻繁, 就有點難找了。

查了一下, 看到這篇提到從 register 設定 conditional break 的方法:

(gdb) b write if 1==$rdi  # stop only when write(1, ...)

Scott 補充了以下的資訊:

要在 syscall argument 上設條件,且要 portable 以目前的工具不容易。用 gdb 的話需能背出『x86-64 上參數依序擺在 rdi, rsi, rdx, rcx, r8d, r9d』。 x86-64 上特別好記,因 syscall 與 一般 function call 被設計成將參數擺在同位置。ARM 上還有 64bit 參數開頭一定擺在偶數暫存器如 r0-r1, r2-r3 而不擺在 r1-r2 等規則。

目前 gdb 沒有將 syscall parameter 存在如 $syscall_arg0 的 convenience variable 中,否則寫: catch syscall write if $syscall_arg0 == 1 即可。

用 "system call calling convention" 當關鍵字查到《What are the calling conventions for UNIX & Linux system calls on x86-64》, 文中有許多相關資訊, 就先備忘吧。

2012年11月24日 星期六

善用 prctl(PR_SET_NAME, name) 協助 debug multi-thread

參考文章: Name your threads

man 2 prtcl 說 prctl(PR_SET_NAME, char*) 會改變 process name, 但是它其實改的是 thread name。使用 gdb 的 info thread 會顯示 thread name, 這對於除錯 multi-thread 很有幫助。需要注意的是, 小心別改到 main thread 的名稱, 避免其它相關 kill 指令或自己寫的 script 失效。

2012-11-25 更新

經 Scott 提醒, 使用 pthread_setname_np(pthread_t, const char*) 更頗當, 且可用來設別的 thread 的名字。注意參數是 pthread_t 不是 tid, 還有不知為何, Ubuntu 12.04 下沒有它的 man page, 但 /usr/include/pthread.h 有它的宣告, 且編譯連結試用後, 也沒有問題。

2012年10月23日 星期二

查 C/C++ symbol 定義的方法

記一下目前習慣找 SYMBOL 定義的方法, 只是模糊的片段經驗, 日後再補充完整一些

用 gj

在命令列打: gj SYMBOL class 或 gj SYMBOL struct 找類別定義

找不到的話, 有可能 SYMBOL 是用 code-gen 或巨集產生的。

  • 檢查一下 mkid 時有沒有漏掉 code-gen 的檔案, 再重建索引: $ mkid -m FILE_LIST ( 格式見 Language map - ID database utilities )
  • 可用 $ g++ -E TARGET.cpp -IINCLUDE_DIR 展開檔案, 再回頭看 SYMBOL 是否是巨集產生的。

另外可用 gj SYMBOL void 找函式定義 (針對無傳回值的情況), 或是 gj SYMBOL 再用 ".h" 過濾只留 header 檔。

Btw, 要找 symbol 被使用的地方的話, 就直接 gj SYMBOL。

用 gdb

在 gdb 裡用 ptype VAR 會列出 VAR 的型別資訊

2013-12-08 更新

後來依自己使用習慣在 gj 加了一些功能, 像是用 gj -d1 SYMBOL 找定義或宣告, 用 gj -s LITERAL 找可能的 symbol 名稱, 詳見 gj 在 github 上的說明。

2012年6月11日 星期一

注意 ptrace 的權限設定

gdb、strace 都是用 ptrace 來監控另一個 process 的情況, 經 wens 提醒關於權限設定的事。

基於安全考量, Ubuntu 10.10 開始, 即使 effective uid 相同, 預設只能對後代 process 用 ptrace。其它情況得用 root 權限執行才可以。在自己的開發機上, 為了方便起見, 可以取消這個限制:

  • 更改目前設定: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope sysctl
  • 開機後自動生效: 改 /etc/sysctl.d/10-ptrace.conf

2012年2月1日 星期三

產生 core dump 的方法

這篇是 "Learning core dump from the hard way", 寫下幾個注意事項, 後來才發現 man core 就可以看到全部東西:

測試

若執行的程式有讀 terminal input, 可直接輸入 ctrl + \ 送出 SIGQUIT。或用指令 kill -QUIT PID 或 kill -ABRT PID, 要求程式產生 core dump, 藉此測試目前的設定是否 ok。

參考《從 /proc/PID/status 了解執行中程式處理 signal 的方式》, 先確認 signal handler 沒有被覆蓋掉, 才可放心測試。

ulimit / setrlimit(RLIMIT_CORE, ...)

Ubuntu 預設為 ulimit -c 0, 表示不產生 core dump, 所以要先執行 ulimit -c unlimited 允許產生 core dump。可以將這行寫到 ~/.bashrc 裡, 以後就預設會產生 core dump。或在程式裡呼叫 setrlimit(RLIMIT_CORE, &limit) 也可以。

/proc/sys/kernel/core_pattern

預設 core dump 的檔名可能不合使用, 參考《setting the core dump name schema》, 可用

$ echo "core.%e.%p.%t" | sudo tee /proc/sys/kernel/core_pattern

改變 core dump 的檔名, 這樣檔名會記錄是程式名稱、PID、發生的時間。在 multi-process 或 multi-thread 時特別有用。若希望每次開機都會生效, 則要在 /etc/sysctl.conf 加入 kernel.core_pattern = core.%e.%p.%t。

core pattern 可以是 "|PROGRAM", 這樣會將 core dump 導到 PROGRAM 的標準輸入, 可以自己寫 PROGRAM 做控制。像是 core dump 太頻繁時, 取樣留下幾個就好, 以免一下就塞爆硬碟。同樣的, %e 那些參數也可以接在 PROGRAM 後當參數用, 像是 "|PROGRAM core.%e.%p.%t", 自己的 PROGRAM 就能從 argv[1] 裡取得適合的檔名。需要特別注意的是, | 和 PROGRAM 之間不可以有空白。

setuid / setgid

若確認 ulimit -c 有設好, core_pattern 沒寫到奇怪的地方 (像是 /dev/null), 硬碟也仍有空間, 卻仍無法產生 core dump, 可能是用到 setuid。用 ls -l 檢查一下, 或用 strace -esetuid 檢查。Linux 為了安全考量, 在使用 setuid/setgid 後, 會自動禁用 core dump。

若確實有觸發 setuid 的話, 在 setuid 後執行:

prctl(PR_SET_DUMPABLE, 1);

重新允許產生 core dump, 應該就 ok 了。

2013-03-26 更新

裝 Ubuntu 12.04 後, 發覺 /etc/sysctl.conf 的設定沒有生效, 查了一下才發覺 apport 覆寫了 /proc/sys/kernel/core_pattern, 參考這裡關掉它, /etc/sysctl.conf 的值應該可如預期作用:

$ vi /etc/default/apport  # 更新成 "enabled=0"

查詢 gdb 的新功能

最近聽 Scott 講到一些 gdb 的好功能, 發現為啥我這裡都沒有, 才發覺原來用的 gdb 版本不同。

要看 gdb 各版的 release note, 得下載 gdb 該版的 tarball, 解開後在 gdb/NEWS 裡有寫詳細的新功能。列一下最近聊到幾個我想用但在 7.2 卻沒有的新功能:

7.3 版: 加速 executable 載入時間的 gdb index:

* GDB now has support for reading and writing a new .gdb_index section. This section holds a fast index of DWARF debugging information and can be used to greatly speed up GDB startup and operation. See the documentation for `save gdb-index' for details.

7.4 版: 不要在離開 scope 時刪掉 watch 的目標變數:

* The "watch" command now accepts an optional "-location" argument. When used, this causes GDB to watch the memory referred to by the exblockquotession. Such a watchpoint is never deleted due to it going out of scope.

2012年1月31日 星期二

gdb: 維持永久的 watchpoint

若想觀察特定變數的變化, watchpoint 比 breakpoint 易於使用。但 watchpoint 有個小問題, 一但離開 scope 後, gdb 會自動刪掉 watchpoint。

若該變數是 member field, 就無法持續追踪不同 method 怎麼讀取或寫入它的值。這篇提供一個小技巧, 取用目標變數的指標, 再觀察指標取值的變數, 藉此讓 gdb 認定它不是區域變數, 借用該文的例子如下:

(gdb) p &var1
$1 = (int *) 0x41523c0
(gdb) watch *(int *)0x41523c0
Hardware watchpoint 1: *(int *)0x41523c0

若真的是區域變數的話, 可用 commands 定義簡短的指令, 之後進入函式時, 自動加回 watchpoint, 借用該文提供的例子:

(gdb) break func
(gdb) commands
>   watch var
>   continue
> end

commands 看來很方便, 還有許多適合應用的情境, 像是追踪網路連線輸出關鍵資訊, 可避免手動操作太久造成 timeout。

2012-02-04 更新

gdb 7.4+ 支援 watch -l, 就不用自己取位置再設 watchpoint 解套。在 7.4 之前想用這功能, 可使用 Scott 寫的 gdb-watch-location.py。用法是

$ wget https://raw.github.com/scottt/scottt-gdb/master/gdb-watch-location.py
$ gdb -x gdb-watch-location.py PROG

在 gdb 裡會新增幾個指令: watch-l、rwatch-l、awatch-l 對應到 watch -l、rwatch -l、awatch -l。

2012年1月22日 星期日

gdb 如何找到 debug symbol

先前在《追踪 glibc 裡的程式》提到自己如何亂試, 試出讓 gdb 讀到 debug symbol。昨天聽 Scott 說明, 才知道背後是怎麼一回事。

在 Ubuntu 下以 libm 為例, 在 /lib/x86_64-linux-gnu/libm-2.13.so 裡面, 先看一些相關的 section header:

$ objdump -h /lib/x86_64-linux-gnu/libm-2.13.so | grep gnu
/lib/x86_64-linux-gnu/libm-2.13.so:     file format elf64-x86-64
  0 .note.gnu.build-id 00000024  0000000000000238  0000000000000238  00000238  2**2
  2 .gnu.hash     00000fa4  0000000000000280  0000000000000280  00000280  2**3
  5 .gnu.version  00000298  00000000000038da  00000000000038da  000038da  2**1
  6 .gnu.version_d 0000005c  0000000000003b78  0000000000003b78  00003b78  2**3
  7 .gnu.version_r 00000030  0000000000003bd8  0000000000003bd8  00003bd8  2**3
 27 .gnu_debuglink 00000014  0000000000000000  0000000000000000  000840e4  2**0

幾個重點

  • .note.gnu.build-id 表示 binary id, 之後用來比對 debug symbol 是否出自 shared lib。看起來 Fedora 在找 debug symbol 時, 有用到 binary id; 而 Ubuntu 沒有的樣子, 我用 hexedit 亂改這個 section 的值, 仍能找到 debug symbol
  • .gnu_debuglink 指向包含 debug symbol 的檔案, 若用 hexedit 改掉它的值, 執行 gdb /lib/x86_64-linux-gnu/libm-2.13.so, gdb 會表示找不到 libm 的 debug symbol
  • 可用 objdump -s -j .gnu_debuglink /lib/x86_64-linux-gnu/libm-2.13.so 顯示 section 內容

若用 LD_PRELOAD=/usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so ANY_PROGRAM 執行程式, 結果會 segmentation fault, 所以我推測 Ubuntu 下 X-dbg 裡包的檔案, 可能和 Fedora 一樣, 只有 debug symbol 而不是完整 strip 前的函式庫。不知要如何確認該檔案裡只有 debug symbol 沒有實際的 object code。

至於確認原本的 binary (object file / shared lib / executable) 是否有編入 debug symbol, 除了用 objdump -S 再找看看有沒有出現程式碼外, 更簡單的作法是用 objdump -h | grep debug:

$ objdump -h /usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so | grep debug
/usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so:     file format elf64-x86-64
 28 .debug_aranges 00004770  0000000000000000  0000000000000000  000002b0  2**4
 29 .debug_pubnames 00002e44  0000000000000000  0000000000000000  00004a20  2**0
 30 .debug_info   000318ee  0000000000000000  0000000000000000  00007864  2**0
 31 .debug_abbrev 00010fc3  0000000000000000  0000000000000000  00039152  2**0
 32 .debug_line   00018c20  0000000000000000  0000000000000000  0004a115  2**0
 33 .debug_str    000041bc  0000000000000000  0000000000000000  00062d35  2**0
 34 .debug_loc    00062bc5  0000000000000000  0000000000000000  00066ef1  2**0
 35 .debug_pubtypes 00003a11  0000000000000000  0000000000000000  000c9ab6  2**0
 36 .debug_ranges 00003e30  0000000000000000  0000000000000000  000cd4c7  2**0

有上述 section 的話, 表示有含 debug symbols。

備註

1. hexedit 基本指令

  • F1: 等同於 man hexedit
  • F4: 跳到 offset, 對照 objdump -h X 看倒數第二欄使用
  • TAB: 切換 hexadecimal 或 ascii 區, 之後取代內容或搜尋, 和這有關
  • 直接在 byte 上打字取代
  • /: 找字串
  • ctrl+c / ctrl+x: 離開 / 存檔離開

2. 見《The DWARF Debugging Standard》了解 debug 資訊如何存在檔案, 只是留著備忘, 目前應該沒必要去讀。

3. 若是自己編含 debug symbol 的函式庫, 就不是上述那一回事了, 而是直接編進目前的函式庫裡。

4. 《Separate Debug Files - Debugging with GDB》說明 gdb 如何支援分離 debug symbol 到另一個檔案, 另外 man strip 或 man objcopy, 可在 "--only-keep-debug" 的部份看到相關說明。看來要知道到底各個 distribution 怎麼做這事, 去看該 distribution 官方的說明會比較確實。之後再看看吧。

2012年1月18日 星期三

自行編譯含 debug symbol 的套件 (package)

對函式庫 X 來說

  • X-dev 表示讓開發者用的, 有裝 header、文件之類的
  • X-dbg 內容類似 X, 不過有留 debug symbol

比方說 libjpeg, 三種的描述如下:

  • libjpeg62 - The Independent JPEG Group's JPEG runtime library
  • libjpeg62-dbg - Development files for the IJG JPEG library
  • libjpeg62-dev - Development files for the IJG JPEG library

裝了 X-dbg 後什麼事也不用做, gdb 自己會優先用含 debug symbol 的版本。不過若官方沒提供供 X-dbg 的話, 就得自己編。參考官方文件《HowToGetABacktrace - Debian Wiki》, 做法如下:

  • $ apt-get install build-essential fakeroot gdb
  • $ apt-get build-dep X
  • $ DEB_BUILD_OPTIONS="nostrip noopt" fakeroot apt-get -b source X
  • $ dpkg -i X.deb

針對上述指令, 補充幾點

  • 編好套件後, 可用 dpkg -c X.deb 先看裡面裝了什麼
  • 用 objdump --source FILE 查看裡面有沒有含程式碼, 有的話才表示確實有含 debug symbol。這個作法比用 file FILE 看是否有 strip 更確實。二進位檔有可能沒含 debug symbol 也沒 strip。

2016/02/16 更新

遇到 "trying to overwrite ..." 的錯誤 時,可以用 sudo dpkg -i --force-overwrite X.deb 強制安裝。

2012年1月12日 星期四

gdb 初步心得

對初學者來說, 最好有個針對常用情境的簡單指南, 之後有閒再看落落長的教學。這裡列一下最近常用的功能, 之後再慢慢更新。

前置動作

  • gcc/g++ 編譯時要加 -g
  • 要觀察用到的函式庫, 則要裝 x-dbg 版 (如 libjpeg62 -> libjpeg62-dbg), gdb 會優先載入 debug 版函式庫。並且需要用 directory 載入原始碼, 詳情見《追踪 glibc 裡的程式》的說明。
  • 若原始碼的位置和當初編 binary 時的位置不同 (常有的事), 可用 set substitute-path from to 做路徑字串取代。
  • 可用 objdump --source FILE 確認是否真的有編到 -g。有的話可以在輸出裡看到程式碼。

雖說通篇我都寫 gdb, 但是 cgdb 好用許多, 推薦使用。聽 command 說 vimgdb 更好用, 不過要 patch vim 後才能用, 就 ... 先備忘吧。

執行方式

從頭執行

  • bash> gdb --args PROGRAM PROGRAM-ARG1 ...
  • gdb> start # 進入 main 後停下來

檢查掛點原因 (參考《產生 core dump 的方法》確保有 core dump)

  • bash> gdb PROGRAM CORE
  • gdb> bt 20 # 看掛掉的 call stack 最底層 20 個 function call

通常載入 PROGRAM 讀 debug symbol 較花時間, 我習慣用 gdb PROGRAM 進 gdb 後, 再用 core CORE 指令看不同的 core dump。

若希望從頭重來, 有設好中斷點就用 r, 沒有則繼續用 start, 不用離開 gdb, 可簡省載入 PROG 的時間。

設中斷點

  • b LOC # 設中斷點, 或用 cgdb 直接在程式視窗按空白鍵
  • i b # 列出全部中斷點
  • d NUM # 移除編號 NUM 的中斷點
  • save breakpoints FILE # 存下目前設的中斷點到檔案 FILE
  • so FILE # 載入之前設的中斷點
  • dis 1-10: 暫停使用 breakpoints 1-10。
  • ena 1-10: 恢復使用 breakpoints 1-10。

關於 LOC: 見《Specify Location - Debugging with GDB》。我比較常用 filename:linenum、linenum 或 +N。因為不易對到 C++ 的函式名稱, 所以我都用行號。

我還沒有適當情境試 conditional break, 但應該很有用, 備忘。

移動

  • n # 跳下行
  • s # 若有函式, 跳進去; 反之則同 n
  • fin # 執行到函式結尾, 返回上一層
  • until LOC # 執行到 LOC 再停, 我以前都傻傻的先設中斷再按 c ...
  • c # 執行到下個中斷點
  • 跳過下一行程式 (ref.), 記得要設中斷點才行
    • b +1
    • j +1
  • ret # 不執行函式剩下的程式, 直接返回上層 frame
  • ps. 按 Enter 可重覆上個指令, 在移動指令時和切 stack frame (後述) 時特別好用

在 call stack 之間移動

  • up # 往上移一個
  • do # 往下移一個
  • f N # 跳到 stack frame N

觀察值

  • p EXPRESSION # 印出 EXPRESSION 的值, 可是變數、函式等
  • 印出 smart pointer 的值 (ref.): 得先取出裡面的 pointer 再取出它的 member function / field
  • whatis VAR # 看型別
  • ptype VAR # 看型別的宣告內容, 了解有那些欄位可讀
  • p P@N # 印出位置 P 開始 N 個變數的值
  • p *P@N # 印出對位置 P 開始 N 個變數取值後的值
  • p *argv@argc # 在 main() 函式裡執行這指令, 會印出命令列參數內容

thread

其它

  • set var X = ... # 執行期間改變變數 X, 以在執行期驗證小修改是否有效, 簡省編譯時間
  • handle SIGSTOP nostop noprint # 收到 SIGSTOP 時不要停且不要輸出訊息, 在 Android 環境有時 gdb 會莫明地一直收到 SIGSTOP

參考資料

進階指令

  • commands BREAKPOINT_ID: 定義在進入 breakpoint 後, 執行一系列指令, 比方 "p some_var; c"。
  • define NEW_CMD: 類似函式, 定義由一堆指令組成的新指令。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...