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

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

2017年3月7日 星期二

除錯技巧:在 Ubuntu 上找出第三方函式庫的程式碼

這篇藉由一個小例子說明如何使用 gdb, locate, apt-file, apt-get 找出問題原因。藉由取得第三方函式庫的程式碼,可以減少瞎猜的時間。

問題

我發現某個程式會不預期的結束。但它不是 crash,沒有 core dump 可看。

用 gdb 找出怎麼結束的

先用 gdb attach 程式,繼續操作。程式結束時 gdb 顯示是呼叫 exit() 結束的。

於是再執行一次,這次用 gdb 在 exit 設中斷點再繼續執行。

取得的 backtrace 如下:

#0  __GI_exit (...) at exit.c:104
#1  0x00007fdd27f60408 in _XDefaultError (...) at ../../src/XlibInt.c:1414
#2  0x00007fdd27f6054b in _XError (...) at ../../src/XlibInt.c:1463
#3  0x00007fdd27f5d5e7 in handle_error ...) at ../../src/xcb_io.c:213
#4  0x00007fdd27f5e687 in _XReply (...) at ../../src/xcb_io.c:699
#5  0x00007fdd27f45346 in XGetWindowProperty (...) at ../../src/GetProp.c:69
#6  0x00007fdd2825db30 in XmuClientWindow () from /usr/lib/x86_64-linux-gnu/libXmu.so.6
...

看 backtrace 沒什麼頭緒,都是第三方函式庫的程式。照 XmuClientWindow() 的說明,它可能會失敗,但它不該直接呼叫 exit()。先找 XmuClientWindow 的原始碼,看看有什麼線索。

用 Ubuntu 的 package 系統找出原始碼

首先用 apt-file 找出 libXmu.so.6 在那個套件 (第一次執行需先跑 apt-file update 更新索引):

$ apt-file search /usr/lib/x86_64-linux-gnu/libXmu.so.6
libxmu6: /usr/lib/x86_64-linux-gnu/libXmu.so.6
libxmu6: /usr/lib/x86_64-linux-gnu/libXmu.so.6.2.0
libxmu6-dbg: /usr/lib/debug/usr/lib/x86_64-linux-gnu/libXmu.so.6.2.0

然後用 apt-get source 取得原始碼:

$ apt-get source libxmu6

在 src/ClientWin.c 裡找到 XmuClientWindow [*1]:

Window
XmuClientWindow(Display *dpy, Window win)
{
    ...
    XGetWindowProperty(dpy, win, WM_STATE, 0, 0, False, AnyPropertyType,
               &type, &format, &nitems, &after, &data);
    ....
}

沒什麼有用資訊,繼續找 XGetWindowProperty 的程式碼。從 backtrace 裡知道它在 ../../src/GetProp.c 裡,但我不知道 GetProp.c 是什麼套件的程式。用 Google 搜尋 GetProp.c 也許會有線索,不過這裡我用另一個作法。

從 header 找出原始碼

src/ClientWin.c 引入的 header 不多,只有這些:

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <X11/Xmu/WinUtil.h>

先用 locate 查出完整路徑:

$ locate X11/Xlib.h
/usr/include/X11/Xlib.h

再用 apt-file 找套件名稱:

$ apt-file search /usr/include/X11/Xlib.h
libx11-dev: /usr/include/X11/Xlib.h

取得原始碼:

$ apt-get source libx11-dev

附帶一提,可透過 gcc -E 展開 #include 驗證 XGetWindowProperty 確實出自 X11/Xlib.h:

$ echo "#include " | gcc - -E | grep XGetWindowProperty
extern int XGetWindowProperty(

header 太多的話,直接編譯原始檔比較快:

$ gcc -E src/ClientWin.c > t
得到的資訊如下:
1674 # 1303 "/usr/include/X11/Xlib.h" 3 4
...
3060 extern int XGetWindowProperty(

找到目標後,往回找第一個 header 路徑就是來源了。如果找不到的話,可能是少指定 include header 的來源目錄。可以從 make 產生的結果得知完整的編譯訊息:

$ ./configure
$ make V=1 | grep ClientWin.c
/bin/bash ../libtool  --tag=CC   --mode=compile gcc -std=gnu99 -DHAVE_CONFIG_H \
-I. -I..  -I../include -I../include/X11/Xmu < ... 略 ...> -c -o ClientWin.lo ClientWin.c
...

找出 -I 的部份,和 -E 一樣加在 gcc 的參數上展開 include。

關於如何讓 make 顯示編譯時的參數 (cflags),參見這裡

繼續追踪程式碼

在 src/GetProp.c 裡發現:

 69     if (!_XReply (dpy, (xReply *) &reply, 0, xFalse)) {

這和前面的 backtrace 一致:

#0  __GI_exit (...) at exit.c:104
#1  0x00007fdd27f60408 in _XDefaultError (...) at ../../src/XlibInt.c:1414
#2  0x00007fdd27f6054b in _XError (...) at ../../src/XlibInt.c:1463
#3  0x00007fdd27f5d5e7 in handle_error ...) at ../../src/xcb_io.c:213
#4  0x00007fdd27f5e687 in _XReply (...) at ../../src/xcb_io.c:699
#5  0x00007fdd27f45346 in XGetWindowProperty (...) at ../../src/GetProp.c:69
#6  0x00007fdd2825db30 in XmuClientWindow () from /usr/lib/x86_64-linux-gnu/libXmu.so.6

表示尋找的方向正確。再繼續看 src/xcb_io.c 和 src/XlibInt.c 內相關函式,得知:

// src/XlibInt.c
1463     rtn_val = (*_XErrorFunction)(dpy, (XErrorEvent *)&event); /* upcall */

對照 backtrace 得知執行 _XErrorFunction 後,是呼叫 _XDefaultError。_XDefaultError 程式如下:

1409 int _XDefaultError(
1410     Display *dpy,
1411     XErrorEvent *event)
1412 {
1413     if (_XPrintDefaultError (dpy, event, stderr) == 0) return 0;
1414     exit(1);
1415     /*NOTREACHED*/
1416 }

確認是這裡呼叫 exit()。所以換掉 _XErrorFunction 可以解決問題。

在程式碼裡搜尋 _XErrorFunction 發現可用 XSetErrorHandler() 設值。這樣至少不會造成程式結束了。

備註

1. 可用 grep 尋找,我是用 gj,找起來更有效率。

2015年8月26日 星期三

找出 node.js 中 block main loop 的程式

環境

問題描述

node.js 的 single thread event loop 架構容易處理 I/O bound, 也避開 multi-thread 的各式問題, 但弱點是 main loop 被卡住了, 整個程式就不會動了。幸好找出錯誤原因的方法滿多的, 若確定是 CPU bound 而不是自己呼叫到 sync API, 也有很多成熟的解法

在開發環境偵錯

node.js 有很多種 profiling 的方法, 試過幾種作法, 我偏好這兩個作法:

兩者都會產生一個 web server 讓你可以連到指定的 port 用網頁作的 UI 看資料。也都不用傳資料到別人機器上。配合 stress test 可以找出潛在的 CPU bound。look 的介面比較簡單, 啟動速度較快。

在上線環境除錯

但若 CPU bound 只有在上線後才會遇到, 就不容易找了。試了一些方法, 覺得用內建的 command line debugger 最管用。

node.js 已內建 debugger, 不需特殊設定就可以用 debugger attach 上線執行的程式。執行方法如下:

$ kill -USR1 PID  # PID 為 node.js pid
$ node debug -p PID

然後執行 pause 暫停程式,接著執行 bt (backtrace) 才會有效。這樣就有線索找出 main loop 卡住的時候在作什麼了。

下面是一個完整的小例子:

$ cat b.js
function fib(n) {
  if (n <= 1) {
    return 1;
  }
  return fib(n - 1) + fib(n - 2);
}

setTimeout(function() {
  console.log(fib(100));
}, 10000);
$ node b.js
Hit SIGUSR1 - starting debugger agent.
debugger listening on port 5858

在另一個 shell 用 debugger:

$ ps auxw | grep node
fcamel          44656   3.2  0.2  3075620  18524 s013  S+    9:23PM
$ kill -USR1 44656
$ node debug -p 44656
connecting... ok
debug> bt
Can't request backtrace now
debug> pause
break in b.js:6
  4   }
  5   return fib(n - 1) + fib(n - 2);
  6 }
  7
  8 setTimeout(function() {
debug> bt
#0 b.js:6:1
#1 b.js:5:10
#2 b.js:5:23
#3 b.js:5:23
#4 b.js:5:23
#5 b.js:5:10
#6 b.js:5:10
#7 b.js:5:23
#8 b.js:5:10
#9 b.js:5:10
debug> repl
Press Ctrl + C to leave debug repl
> n
6

不過 CPU 太忙的時候 kill -USR1 好像會失效 (node.js 沒有顯示收到 SIGUSR1 進入 debug mode), 然後 debugger attach 會失敗。或是 debugger attach 成功, 有時 pause 也會失敗, 像這樣卡住沒反應:

debug> pause
break in [unnamed]:1
  1

有閒再深入了解原因。

在 Fedora 下裝 id-utils

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