《Linux调试工具lsof的深入分析》

1)查看对某个文件的使用情况

-------------------------------------------------------
root@troy:/# lsof -w /etc/passwd

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
python  2340 troy   10r   REG    8,1     1804 2886346 /etc/passwd
-------------------------------------------------------
注:-w参数表示不打印警告信息.
lsof程序会打开/proc/PID/fd/目录找到对映的文件句柄,再通过stat系统调用得到文件的详细信息.
同时还会利用fdinfo目录下的文件句柄得到打开文件时的状态.
例如:

1.1)创建程序test.c,本程序在/tmp/目录下以O_WRONLY(只写)和O_SYNC(同步)方式打开temp文件.
-------------------------------------------------------
root@troy:/tmp# more test.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main ()
{
        int fd;
        fd = open("/tmp/temp", O_WRONLY|O_SYNC);
        lseek(fd, 80L, SEEK_SET);
        sleep(100);
        close(fd);

        return 0;
}

root@troy:/tmp# gcc test.c -o testfd
root@troy:/tmp# ./testfd &
-------------------------------------------------------

1.2)观察testfd程序打开的文件/tmp/temp
-------------------------------------------------------
cat /proc/$(lsof -w /tmp/temp|awk '/testfd/{print $2}')/fdinfo/3
pos: 80
flags: 0110001
-------------------------------------------------------
我们看到程序输出pos和flags两组数据,pos字段表示被打开文件的当前读写位置,flags表示以文件方式打开该文件.
pos为80表示从文件起始位置移动80个字节的位置.
flags为0110001中的xxxxxx1表示以只写方式打开文件,如果是只读则为xxxxxx0,如果可读可写则为xxxxxx2,
其中的xx1xxxx表示以同步方式(O_SYNC)打开文件,如果改用以O_ASYNC方式打开文件,则为xx2xxxx,
如果同时指定了O_SYNC和O_ASYNC两种方式打开文件,结果则为xx3xxxx.
如果我们增加了O_APPEND方式打开文件,结果则为xxx2xxx.
如果我们增加了O_NONBLOCK方式打开文件,结果则为xxx4xxx.
如果同时指定了O_APPEND和NONBLOCK两种方式,结果则为xxx6xxx.
最后指明一下O_ASYNC用于打开终端和socket文件,默认产生SIGIO信号.
而O_NONBLOCK表示不阻塞打开的文件,只用于FIFO的管道文件中.
 

2)查看对某个目录的使用情况

我们先执行上个试验的程序.

-------------------------------------------------------
root@troy:~# /tmp/testfd &
[1] 17021

root@troy:~# lsof +d /tmp/
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF   NODE NAME
gedit    2531 troy    7u  unix 0xffff880029a49b00      0t0  17106 /tmp/gedit.troy.917415843
testfd  17021 root  txt    REG                8,1     8617 262475 /tmp/testfd
testfd  17021 root    3w   REG                8,1        0 262412 /tmp/temp
bash    3926  troy  cwd    DIR                8,1     4096 262149 /tmp
bash    4001  troy  cwd    DIR                8,1     4096 262149 /tmp
-------------------------------------------------------

如果不加参数+d呢?这里我们只看到了用户的当前使用目录,如下:

-------------------------------------------------------
root@troy:~# lsof -w /tmp
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
bash    3926 troy  cwd    DIR    8,1     4096 262149 /tmp
bash    4001 troy  cwd    DIR    8,1     4096 262149 /tmp
-------------------------------------------------------

有什么区别吗?
加+d参数的情况下,lsof会遍列所有的进程,以及进程下所有的fd,fdinfo,cwd,root,exec,maps,以查找被程序占用中的目录.
而不加参数+d的情况下,lsof只会遍列所有进程的cwd,cwd是用户当前目录的软链接.如下:

-------------------------------------------------------
root@troy:~# ls -l /proc/3926/cwd
lrwxrwxrwx 1 troy troy 0 2011-02-09 19:23 /proc/3926/cwd -> /tmp
-------------------------------------------------------

 

3)查看某个进程的使用情况
-------------------------------------------------------
troy@troy:/proc/23294$ lsof -p 23294
COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF    NODE NAME
ssh     23294 troy  cwd    DIR     8,1     4096  262149 /tmp
ssh     23294 troy  rtd    DIR     8,1     4096       2 /
ssh     23294 troy  txt    REG     8,1   339712 1058082 /usr/bin/ssh
ssh     23294 troy  mem    REG     8,1    51712  787472 /lib/libnss_files-2.11.1.so
ssh     23294 troy  mem    REG     8,1    43552  787456 /lib/libnss_nis-2.11.1.so
ssh     23294 troy  mem    REG     8,1    97256  787430 /lib/libnsl-2.11.1.so
ssh     23294 troy  mem    REG     8,1    35712  787454 /lib/libnss_compat-2.11.1.so
ssh     23294 troy  mem    REG     8,1   135745  787466 /lib/libpthread-2.11.1.so
ssh     23294 troy  mem    REG     8,1    10224  787804 /lib/libkeyutils-1.2.so
ssh     23294 troy  mem    REG     8,1    31168 1050775 /usr/lib/libkrb5support.so.0.1
ssh     23294 troy  mem    REG     8,1    14584  798518 /lib/libcom_err.so.2.1
ssh     23294 troy  mem    REG     8,1   154048 1050756 /usr/lib/libk5crypto.so.3.1
ssh     23294 troy  mem    REG     8,1   803192 1050769 /usr/lib/libkrb5.so.3.3
ssh     23294 troy  mem    REG     8,1    14696  795838 /lib/libdl-2.11.1.so
ssh     23294 troy  mem    REG     8,1  1572232  787469 /lib/libc-2.11.1.so
ssh     23294 troy  mem    REG     8,1   213784 1050761 /usr/lib/libgssapi_krb5.so.2.2
ssh     23294 troy  mem    REG     8,1    93000  787444 /lib/libresolv-2.11.1.so
ssh     23294 troy  mem    REG     8,1    92752  786985 /lib/libz.so.1.2.3.3
ssh     23294 troy  mem    REG     8,1  1622304  796093 /lib/libcrypto.so.0.9.8
ssh     23294 troy  mem    REG     8,1   136936  787431 /lib/ld-2.11.1.so
ssh     23294 troy    0u   CHR  136,20      0t0      23 /dev/pts/20
ssh     23294 troy    1u   CHR  136,20      0t0      23 /dev/pts/20
ssh     23294 troy    2u   CHR  136,20      0t0      23 /dev/pts/20
ssh     23294 troy    3u  IPv4 1603924      0t0     TCP troy.local:41879->10.1.1.7:ssh (ESTABLISHED)
ssh     23294 troy    4w  FIFO     0,8      0t0 1603904 pipe
ssh     23294 troy    5u   CHR  136,19      0t0      22 /dev/pts/19
ssh     23294 troy    6r   CHR     5,0      0t0    1129 /dev/tty
ssh     23294 troy    7u   CHR  136,21      0t0      24 /dev/pts/21
ssh     23294 troy    8u   CHR  136,20      0t0      23 /dev/pts/20
ssh     23294 troy    9u   CHR  136,20      0t0      23 /dev/pts/20
ssh     23294 troy   10u   CHR  136,20      0t0      23 /dev/pts/20
-------------------------------------------------------

lsof程序会跟据用户指定的PID,遍列/proc/目录找到该PID,在/proc/PID目录下依次打开stat,maps,fd,fdinfo.
stat文件包含了当前进程的信息.
当前进程的stat如下:
23294 (ssh) S 23291 23294 23294 34837 23294 4202496 836 0 0 0 8 10 0 0 20 0 1 0 29282141 39632896 658 18446744073709551615 140114439372800 140114439700540 140733214155456 140733214149016 140114423930835 0 0 4096 136331271 18446744071580239593 0 0 17 1 0 0 0 0 0

maps文件包含映像的文件:
当前进程的maps如下:
7f6eecf42000-7f6eecf4e000 r-xp 00000000 08:01 787472                     /lib/libnss_files-2.11.1.so
7f6eecf4e000-7f6eed14d000 ---p 0000c000 08:01 787472                     /lib/libnss_files-2.11.1.so
7f6eed14d000-7f6eed14e000 r--p 0000b000 08:01 787472                     /lib/libnss_files-2.11.1.so
7f6eed14e000-7f6eed14f000 rw-p 0000c000 08:01 787472                     /lib/libnss_files-2.11.1.so
7f6eed14f000-7f6eed159000 r-xp 00000000 08:01 787456                     /lib/libnss_nis-2.11.1.so
7f6eed159000-7f6eed358000 ---p 0000a000 08:01 787456                     /lib/libnss_nis-2.11.1.so
7f6eed358000-7f6eed359000 r--p 00009000 08:01 787456                     /lib/libnss_nis-2.11.1.so
7f6eed359000-7f6eed35a000 rw-p 0000a000 08:01 787456                     /lib/libnss_nis-2.11.1.so
7f6eed35a000-7f6eed371000 r-xp 00000000 08:01 787430                     /lib/libnsl-2.11.1.so
7f6eed371000-7f6eed570000 ---p 00017000 08:01 787430                     /lib/libnsl-2.11.1.so
7f6eed570000-7f6eed571000 r--p 00016000 08:01 787430                     /lib/libnsl-2.11.1.so
7f6eed571000-7f6eed572000 rw-p 00017000 08:01 787430                     /lib/libnsl-2.11.1.so
后省略.
这里用pmap 23294也可以找到加载的文件与虚拟地址的对映.

fd,fdinfo我们已经分析过了,当lsof找到/proc/23294/3时,发现是个socket文件,如下:

----------------------------------------------------
troy@troy:/proc/23294/fd$ ls -l
total 0
lrwx------ 1 troy troy 64 2011-02-11 02:47 0 -> /dev/pts/20
lrwx------ 1 troy troy 64 2011-02-11 02:47 1 -> /dev/pts/20
lrwx------ 1 troy troy 64 2011-02-11 02:47 10 -> /dev/pts/20
lrwx------ 1 troy troy 64 2011-02-11 01:07 2 -> /dev/pts/20
lrwx------ 1 troy troy 64 2011-02-11 02:47 3 -> socket:[1603924]
l-wx------ 1 troy troy 64 2011-02-11 02:47 4 -> pipe:[1603904]
lrwx------ 1 troy troy 64 2011-02-11 02:47 5 -> /dev/pts/19
lr-x------ 1 troy troy 64 2011-02-11 02:47 6 -> /dev/tty
lrwx------ 1 troy troy 64 2011-02-11 02:47 7 -> /dev/pts/21
lrwx------ 1 troy troy 64 2011-02-11 02:47 8 -> /dev/pts/20
lrwx------ 1 troy troy 64 2011-02-11 02:47 9 -> /dev/pts/20
----------------------------------------------------

此时会依次打开以下的文件,对网络套字接进行分析,各文件作用如下:
/proc/net/raw ---->原始套接字
/proc/net/unix ---->UNIX套接字
/proc/net/sockstat ---->当前套接字的使用情况
/proc/net/tcp ---->TCP套接字
/proc/net/udp ---->UDP套接字
/proc/net/udplite --->UDP无线通讯套接字

在/proc/net/tcp会找到对映的inode,socket:[1603924]的inode为1603924,而tcp中sl为12的一行正是这个socket服务.如下:
----------------------------------------------------
more /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                    
   0: 00000000:0087 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 8984 1 ffff8800677d1380 300 0 0 2 -1                     
   1: 00000000:0369 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 8575 1 ffff8800677d0d00 300 0 0 2 -1                     
   2: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 8673 1 ffff880068c51a00 300 0 0 2 -1                     
   3: 00000000:1770 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 5350 1 ffff880068c50680 300 0 0 2 -1                     
   4: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 4315 1 ffff880068c50000 300 0 0 2 -1                     
   5: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 25571 1 ffff88001a06c780 300 0 0 2 -1                    
   6: 00000000:0BB8 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 7881 1 ffff8800677d0680 300 0 0 2 -1                     
   7: 00000000:BD59 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 8986 1 ffff880068c52080 300 0 0 2 -1                     
   8: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 6304 1 ffff880068c50d00 300 0 0 2 -1                     
   9: 4506010A:D6E9 0701010A:0016 01 00000000:00000000 02:00028405 00000000  1502        0 1603452 2 ffff880057896180 22 4 8 5 -1                   
  10: 4506010A:89A5 0701010A:0016 01 00000000:00000000 02:00091EFF 00000000  1502        0 1672803 2 ffff88001a068680 25 4 18 4 -1                  
  11: 4506010A:872E 0701010A:0016 01 00000000:00000000 02:0004B77C 00000000  1502        0 1177942 2 ffff880057893a80 125 4 0 3 2                   
  12: 4506010A:A397 0701010A:0016 01 00000000:00000000 02:0004D6A6 00000000  1502        0 1603924 2 ffff880057894e00 23 4 0 5 -1                   
  13: 4506010A:BD31 0701010A:0016 01 00000000:00000000 02:000139A5 00000000  1502        0 1345002 2 ffff880057897500 22 4 0 5 -1                   
  14: 4506010A:8F21 0701010A:0016 01 00000000:00000000 02:000192BE 00000000  1502        0 1351794 2 ffff880057896800 25 4 12 4 -1                  
  15: 4506010A:8AD1 0701010A:0016 01 00000000:00000000 02:00094915 00000000  1502        0 1447912 2 ffff880068c55b00 21 4 14 4 -1
----------------------------------------------------
其中local_address代表本地的IP和端口
4506010A(本地地址)的45转化为十进制是69,06转化为十制制是6,01转化为十进制是1,0A转化为十进制是10,倒过来也就是10.1.1.69,是本地地址.
A397(本地端口)转化为十进制是41879,也就是本地端口
rem_address代表远程IP和端口
0701010A(远程地址):10.1.1.7
0016(远程端口)22
其它参数都是内核调试时使用
uid就是用户的UID

 

4)显示所属user进程打开的文件

----------------------------------------------------
root@troy:/proc/2554# lsof -u test -w
COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF    NODE NAME
su      2554 test  cwd    DIR                8,1     4096       2 /
su      2554 test  rtd    DIR                8,1     4096       2 /
su      2554 test  txt    REG                8,1    36864  262185 /bin/su
su      2554 test  mem    REG                8,1    27024  797476 /lib/libnss_lsass.so.2.0.0
su      2554 test  mem    REG                8,1    10272 1102794 /usr/lib/gconv/IBM850.so
su      2554 test  mem    REG                8,1    14392 1102782 /usr/lib/gconv/UTF-16.so
su      2554 test  mem    REG                8,1    43528  795516 /lib/security/pam_gnome_keyring.so
su      2554 test  mem    REG                8,1   256768  787477 /lib/libdbus-1.so.3.4.0
su      2554 test  mem    REG                8,1    14536 1052877 /usr/lib/libck-connector.so.0.0.0
su      2554 test  mem    REG                8,1    10360  795488 /lib/security/pam_ck_connector.so
su      2554 test  mem    REG                8,1    14344  787928 /lib/libgpg-error.so.0.4.0
su      2554 test  mem    REG                8,1   491000  787918 /lib/libgcrypt.so.11.5.2
su      2554 test  mem    REG                8,1    92752  786985 /lib/libz.so.1.2.3.3
su      2554 test  mem    REG                8,1    67896 1055840 /usr/lib/libtasn1.so.3.1.7
su      2554 test  mem    REG                8,1    10224  787804 /lib/libkeyutils-1.2.so
su      2554 test  mem    REG                8,1    31168 1050775 /usr/lib/libkrb5support.so.0.1
su      2554 test  mem    REG                8,1    14584  798518 /lib/libcom_err.so.2.1
以下略
----------------------------------------------------

通过strace,我们知道lsof通过stat系统调用得到这个进程的目录owner,正是我们要找的用户进程.

如下:
stat("/proc/2554/", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/2554/stat", O_RDONLY)       = 4
read(4, "2554 (su) S 2533 2554 2533 34817"..., 4096) = 241

最后lsof通过调用cwd,root,fd,fdinfo,maps以及网络套接字显示输出用户进程所使用的文件.

 

5)显示网络服务

----------------------------------------------------
root@troy:~# lsof -i
COMMAND    PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
smbd       767        root   22u  IPv6   4410      0t0  TCP *:microsoft-ds (LISTEN)
smbd       767        root   23u  IPv6   4412      0t0  TCP *:netbios-ssn (LISTEN)
sshd       787        root    3u  IPv4   4227      0t0  TCP *:ssh (LISTEN)
sshd       787        root    4u  IPv6   4229      0t0  TCP *:ssh (LISTEN)
avahi-dae  809       avahi   13u  IPv4   4568      0t0  UDP *:mdns
avahi-dae  809       avahi   14u  IPv4   4569      0t0  UDP *:50511
dhclient   883        root    5u  IPv4   4530      0t0  UDP *:bootpc
Xorg       912        root    1u  IPv6   4705      0t0  TCP *:x11 (LISTEN)
Xorg       912        root    3u  IPv4   4706      0t0  TCP *:x11 (LISTEN)
dcerpcd   1132        root   15u  IPv4   8845      0t0  TCP *:loc-srv (LISTEN)
dcerpcd   1132        root   16u  IPv4   8846      0t0  UDP *:loc-srv
eventlogd 1261        root   14u  IPv4   8850      0t0  TCP *:55846 (LISTEN)
exim4     1563 Debian-exim    3u  IPv4   6359      0t0  TCP localhost:smtp (LISTEN)
以下略
----------------------------------------------------

lsof通过遍列所有进程的所有文件句柄,找到网络套接字,再通过/proc/net/下面的网络信息得到具体的套接字信息.