2010年2月23日 星期二

用 strace 和 ltrace 找出用到的 system call 和 library call

前面提到 host 沒有 call gethostbyaddr, 面惡心善的 Scott 大概是查覺我下載了原始碼, 卻沒有找出確認它的方法。於是在另一篇留言裡說可以用 strace、ltrace 或 gdb 輕易做到這事 (幸好我還沒開始試 profiler 啊...)。

strace 和 ltrace 顧名思議, 它們會列出執行期間用到的 system / library call。若不確定有興趣的函式是那個, 可以用 man page 編號來判別。比方 man gethostbyaddr 顯示被分在  section 3 下, 所以 gethostbyaddr 是 library call [*1]。

分別拿 host 和對照組 getent 試的結果, 可以看到 getent 有打開 /etc/hosts (從 strace 那看到的), 並呼叫 gethostbyaddr;而 host 卻兩者皆無。解開疑惑實乃人生一大痛快之事, 感謝 Scott 的指點。

附上參考用指令:
$ ltrace getent hosts 127.0.0.1 2>&1 | grep gethostby
gethostbyaddr("\177", 4, 2)                      = 0xb7ed0aa0
$ strace getent hosts 127.0.0.1 2>&1 | grep "/etc/hosts"
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3

$ strace host 127.0.0.1 2>&1 | grep "/etc/hosts"
$ ltrace host 127.0.0.1 2>&1 | grep gethostby

另外我好奇之下試了傳說中人人都會寫的 C 版 「Hello, world! 」, 結果發現 compiler 很聰明地用 puts 而非 printf。

參考程式如下:

#include <stdio.h>

int main(void)
{
    printf("hello, world!\n");
    puts("hello, world!\n");
    printf("hello, world! %d\n", 3);
    return 0;
}

執行結果:
$ ltrace ./f > /dev/null
__libc_start_main(0x80483f4, 1, 0xbf812ab4, 0x8048450, 0x8048440 
puts("hello, world!")                            = 14
puts("hello, world!\n")                          = 15
printf("hello, world! %d\n", 3)                  = 16
+++ exited (status 0) +++

接著我用 ltrace 執行 python (ltrace python -c ''), 結果 ltrace 狂噴訊息卻不會停......, 今日閒暇時間用盡這待那天有緣再來研究吧。

2010-02-25 更新

Scott 在留言裡提到也可以用 LD_PRELOAD  抽換動態載入的函式來達到同樣目的 (確認是否有呼叫 gethostbyaddr)。範例程式如下 (稍微修正 Scott 的範例讓它在我的機器能正常 compile):
$ printf '#include <stdio.h>\n#include <assert.h>\nvoid gethostbyaddr(void) { assert(0); }\n' > t.c
$ gcc -fPIC -shared t.c -o t.so
$ LD_PRELOAD=./t.so getent hosts 127.0.0.1
getent: t.c:3: gethostbyaddr: Assertion `0' failed.
Aborted
$ LD_PRELOAD=./t.so host 127.0.0.1
1.0.0.127.in-addr.arpa domain name pointer localhost.

上面的範例透過 LD_PRELOAD 讓程式改用自訂的 gethostbyaddr。如此一來, 抽換掉懷疑的函式再執行指令, 就知道是否有用到了。LD_PRELOAD 的詳細說明可參考 jserv 翻譯的 《Modifying a Dynamic Library Without Changing the Source Code / 在不更動原始程式碼的前提下,修改動態程式庫》

備註
  1. man man 可看到各 section 的含意, 摘錄如下:
    1 Executable programs or shell commands
    2 System calls (functions provided by the kernel)
    3 Library calls (functions within program libraries)
    4 Special files (usually found in /dev)
    5 File formats and conventions eg /etc/passwd
    6 Games
    7 Miscellaneous (including macro packages and conven-
    tions), e.g. man(7), groff(7)
    8 System administration commands (usually only for root)
    9 Kernel routines [Non standard]

    總共也才九節, 遊戲竟然自成一節......。

2012-01-30 更新

見 tag strace 下的新文章, 了解比較詳細的 strace 使用例子。

3 則留言:

  1. Please allow me to show you one more trick:

    [scottt@xpc65 ~]$ printf '#include \nvoid gethostbyaddr(void) { assert(0); }\n' > t.c

    [scottt@xpc65 ~]$ gcc -fPIC -shared t.c -o t.so

    [scottt@xpc65 ~]$ LD_PRELOAD=./t.so getent hosts 127.0.0.1
    getent: t.c:2: gethostbyaddr: Assertion `0' failed.
    Aborted (core dumped)

    [scottt@xpc65 ~]$ LD_PRELOAD=./t.so host 127.0.0.1
    1.0.0.127.in-addr.arpa domain name pointer localhost.scottt.


    Maybe update this blog post to explain this result?

    回覆刪除
  2. Sure. I'll study the related stuff and update this post tonight. It seems like you use LD_PRELOAD to override the function. I originally thought that's easy to do in Python, but I didn't know that it's also an easy job in C.

    回覆刪除
  3. LD_PRELOAD is not as general a mechanism as wrapping a function in Python so you should make sure to note its limitations as well.

    回覆刪除

在 Fedora 下裝 id-utils

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