用 SystemTap 找出送 SIGKILL 的 process

fcamel
fcamel的程式開發心得
9 min readNov 10, 2017

--

問題

Linux 上的 process 執行一陣子後掛了。一般情況可以用 gdb attach process,這樣在 process 掛的時候,gdb 會顯示是怎麼掛的。比方說 segfault,或是收到別人送的 signal。若是 segfault 就看 backtrace 找原因; 收到 signal 就用 sigaction()註冊 handler,從 siginfo_t.si_pid 找出是誰送 signal 來的。然而,SIGKILL 和 SIGSTOP 是無法攔截的,若是被 SIGKILL 殺掉,無法用 gdb 找出是那個 process 發出 SIGKILL。

SystemTap

SystemTap 提供觀察 kernel 內部狀態,它的運作原理是將 SystemTap script 編成 C 程式、編成 kernel module、載入 kernel,然後在觀察結束後再移除剛載入的 kernel module。所有 process 送出 SIGKILL 都得經過 kernel,所以我們可以用 SystemTap 注入觀察程式,找出誰呼叫 SIGKILL。

在 Ubuntu 16.04 的安裝方法很簡單,照官方文件作即可:

$ sudo apt-get install -y systemtap gcc$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C8CAB6595FDFF622$ codename=$(lsb_release -c | awk  '{print $2}')
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename} main restricted universe multiverse
#deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF

sudo apt-get update
sudo apt-get install linux-image-$(uname -r)-dbgsym

後面那段 codename … 是產生 /etc/apt/sources.list.d/ddebs.list,裡面定義kernel debug image 套件的來源。注意我這邊註解掉 ${codename}-security 那行,有這行的時候,apt-get update 會回報錯誤:

W: The repository 'http://ddebs.ubuntu.com xenial-security Release' does not have a Release file.
N: Data from such a repository can't be authenticated and is therefore potentially dangerous to use.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: Failed to fetch http://ddebs.ubuntu.com/dists/xenial-security/main/binary-amd64/Packages 404 Not Found
E: Some index files failed to download. They have been ignored, or old ones used instead.

移掉後才能正常執行 apt-get update,然後就可以安裝 kernel debug image (我對到的版本是 linux-image-4.4.0–98-generic-dbgsym )。

測試 SystemTap 是否正常運作:

$ cat hello.tap
probe timer.s(1)
{
printf("hello world\n")
}
$ sudo stap hello.tap
[sudo] password for fcamel:
hello world
hello world
hello world
^C

找出送出 SIGKILL 的 process

我們知道 Linux 程式會用 kill() 送 signal,所以偵測呼叫 kill() 的地方即可:

$ cat who_call_kill.tap
probe syscall.kill
{
printf("%s(%d) call kill(%s)\n", execname(), pid(), argstr)
}

$ sudo stap who_call_kill.tap

然後在第二個 bash 執行 cat,在第三個 bash 用 ps 找出 cat 的 pid 並用 kill -9 殺掉 cat,再回來看 stap 執行的結果:

$ sudo stap who_call_kill.tap
screen(25606) call kill(25607, SIG_0)
screen(3385) call kill(2888, SIG_0)
screen(25606) call kill(25607, SIG_0)
bash(8352) call kill(30393, SIGKILL)
screen(3385) call kill(2888, SIG_0)
^Cstap(29954) call kill(30356, SIGTERM)

中間的 bash(8352) call kill(30393, SIGKILL) 就是我們要的結果。相關參數可以參考官方 reference Chater35. syscalls

更可靠的作法是用 SystemTap 的 signal API,畢竟送出 signal 的方法不只有 kill() 而已。改用 signal API 的作法如下:

$ cat who_send_signal.tap
probe signal.send{
printf("%s by %s: %s(%d) -> %s(%d)\n", sig_name, name, execname(), pid(), pid_name, sig_pid);
}
$ sudo stap who_send_signal.tap
SIGCHLD by signal_generate: lpstat(31834) -> sh(31833)
...
SIGKILL by signal_generate: bash(8352) -> cat(31848)
...
^CSIGINT by signal_generate: kworker/u64:1(28733) -> stapio(31829)
SIGINT by signal_generate: kworker/u64:1(28733) -> stap(31825)
SIGINT by signal_generate: kworker/u64:1(28733) -> sudo(31824)
SIGTERM by signal_generate: stap(31825) -> stapio(31829)

相關參數可參考 prob::signal.send

備註: 自行安裝 SystemTap

如果遇到問題懷疑是 Ubuntu 內建版本有問題的話,可以自行編 SystemTap。這裡有人提到這問題,並提供安裝步驟。備忘一下:

## Install build-required packages
$ apt-get update && \
apt-get install -y build-essential gettext elfutils libdw-dev python wget tar && \
apt-get clean;

## Build from source
$ wget https://sourceware.org/systemtap/ftp/releases/systemtap-3.1.tar.gz
$ tar xzvf systemtap-3.1.tar.gz

## Instruction: https://sourceware.org/git/?p=systemtap.git;a=blob_plain;f=README;hb=HEAD
$ cd systemtap-3.1/ && \
./configure && \
make all
## Install by checkinstall
$ sudo checkinstall

最後一步我改用 checkinstall,這樣會包成 deb 再裝進去,方便日後移除。

參考資料

相關文章

--

--