[Linux] 找出執行的程式中,哪邊在建立 Unix domain socket
今天遇到客戶回報一個奇怪的問題,
專案的 Python 程序似乎開了許多檔案,把 fd 都用光了,
導致程序再也不能開啟新的檔案…
看一下 /proc/<PID>/fd,發現有許多 socket 檔案,
下面只列出其中一個:
# ll /proc/40931/fd/ lrwx------ 1 root root 64 Jan 17 16:42 12 -> socket:[383853277]
但這 socket 檔是連到哪去呢?
用 lsof
去找那個 socket fd 話,可以知道它是一個 unix domain socket,
但不知道它的路徑:
# lsof -n | egrep 383853277 COMMAND PID TID USER FD TYPE DEVICE SIZE/OFF NODE NAME python3 40931 root 12u unix 0xffff8e346aee5500 0t0 383853277 socket
用 netstat
去找那個 socket fd 的話,得到的資訊也差不多:
# netstat -anp -x Active UNIX domain sockets (servers and established) Proto RefCnt Flags Type State I-Node PID/Program name Path unix 3 [ ] STREAM CONNECTED 383853277 40931/python3
這要怎麼才能找到,是哪邊產生了這個 unix domain socket 呢?
研究了一下,想到的有下面幾個:
1. 用單步執行
如果這是在程式一跑起來時,就會產生的 unix domain socket,
可以試著單步執行程式 (像是使用 python 的 pdb),來找出是哪一段程式造成的。
但如果是程式跑了好一陣子才出來的,這個方法就不太適用…
2. 用 strace 指令
假設那個 unix domain socket 的 fd 都蠻固定的,
比如說,都出現在 /proc/<PID>/fd/12 的話,
那我們可以用 strace
指令監看 open 和 socket 這兩個 API,
然後找出 fd = 12 的前後文,來推敲出可能的程式區段。
以下面為例,fd = 12 的出現了好幾次,
這是因為可能開檔 fd = 12 之後又關檔、再次開檔時就還是使用了 12 這個空位。
可以看到 socket() API 被呼叫後也產生出了 fd = 12,
接著 fd 在載入 libf.so 和 libg.so 後穩定變成 13 ,
因此可以從程式裡面,去找出載入 libf.so 和 libg.so 的地方,
往前推看看哪邊可能會建立 unix domain socket:
# strace -f -e open,socket python3 test.py ...... [pid 3906] open("/project/log/u.log.lock", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 12 [pid 3906] socket(PF_LOCAL, SOCK_STREAM, 0) = 12 [pid 3906] socket(PF_LOCAL, SOCK_STREAM, 0) = 13 [pid 3906] socket(PF_LOCAL, SOCK_STREAM, 0) = 13 [pid 3906] open("/project/libf.so", O_RDONLY|O_CLOEXEC) = 13 [pid 3906] open("/project/libg.so", O_RDONLY|O_CLOEXEC) = 13
3. 使用 ss 指令
這個 ss
就是我這次學到的新指令,
它可以將系統中所有的 socket 都列出來,非常的好用~
先看一下它的參數說明:
# ss -h Usage: ss [ OPTIONS ] ss [ OPTIONS ] [ FILTER ] -h, --help this message -V, --version output version information -n, --numeric don't resolve service names -r, --resolve resolve host names -a, --all display all sockets -l, --listening display listening sockets -o, --options show timer information -e, --extended show detailed socket information -m, --memory show socket memory usage -p, --processes show process using socket -i, --info show internal TCP information -s, --summary show socket usage summary -b, --bpf show bpf filter socket information -4, --ipv4 display only IP version 4 sockets -6, --ipv6 display only IP version 6 sockets -0, --packet display PACKET sockets -t, --tcp display only TCP sockets -u, --udp display only UDP sockets -d, --dccp display only DCCP sockets -w, --raw display only RAW sockets -x, --unix display only Unix domain sockets -f, --family=FAMILY display sockets of type FAMILY -A, --query=QUERY, --socket=QUERY QUERY := {all|inet|tcp|udp|raw|unix|packet|netlink}[,QUERY] -D, --diag=FILE Dump raw information about TCP sockets to FILE -F, --filter=FILE read filter information from FILE FILTER := [ state TCP-STATE ] [ EXPRESSION ]
像我們要找 unix domain socket,可以下 -x 參數,
加上 -e 參數可以顯示出更多資訊,
如果想把 listen port 這種的也列出來,還可以加上 -a 或 -l 參數。
不過像我們這次是要找的是已連線的 unix domain socket,因此下 -x 和 -e 參數就好了。
如下所示,我們本來從 /proc/<PID>/fd 裡看到 fd = 12 時,內容是 socket:[383853277]。
用 383853277 在 ss 指令結果裡搜尋,
可以看到跟它對口 domain socket,是綁在 /tmp/.s.PGSQL.5432 這個路徑上。
而這個是 Python 的 psycopg2 模組建立出來的 domain socket:
# ss -x -e Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port u_str ESTAB 0 0 * 383853277 * 383854296 <-> u_str ESTAB 0 0 /tmp/.s.PGSQL.5432 383854296 * 383853277 <->
用以上方式,可能就可以找出來是哪邊在建立 unix domain socket,
這樣如果有沒關閉的 socket,也比較有機會找出來囉~
參考資訊:Find out details of a socket by the socket’s path name?