我们如何知道谁在伪终端设备的另一端?

我们如何知道谁在伪终端设备的另一端?

如果我做一个:

echo foo > /dev/pts/12

某些进程会将其foo\n从其文件描述符读取到主端。

有没有办法找出那个(那些)进程是什么?

或者换句话说,我如何找出哪个 xterm/sshd/script/screen/tmux/expect/socat... 位于另一端/dev/pts/12

lsof /dev/ptmx会告诉我在任何 pty 的主端有文件描述符的进程。进程本身可以使用ptsname()TIOCGPTNioctl )根据其自己的 fd 找到主设备,因此我可以使用:

gdb --batch --pid "$the_pid" -ex "print ptsname($the_fd)"

对于每个返回的 pid/fd 来lsof构建该映射,但是是否有更直接、可靠且侵入性较小的方式来获取该信息?

答案1

起初,我尝试根据我找到的信息追踪 pid,xterm但它是松散的。我的意思是,我认为它有效,但充其量只是环境 - 我不完全理解该文件提供的所有信息,并且仅匹配其内容和已知终端进程之间似乎对应的信息。xterm/proc/locks

然后我尝试观察pty 之间的lsof/strace活动进程。write/talk我以前从未真正使用过这两个程序,但它们似乎依赖于utmp.如果我的目标 pty 出于某种原因没有utmp条目,他们都拒绝承认它的存在。也许有办法解决这个问题,但我很困惑而放弃了它。

我尝试udevadm使用 136 和 128 个主设备节点进行一些发现,分别在 和 中进行了广告ptsptm/proc/tty/drivers我也缺乏使用该工具的任何非常有用的经验,并且再次没有发现任何实质性内容。但有趣的是,我注意到:min这两种设备类型的范围都以惊人的价格列出0-1048575

直到我重新审视这个这个内核文档不过,我开始从 s 的角度思考这个问题mount。我之前已经读过好几次了,但是当对该领域的继续研究使我发现这一点时这个 2012 年/dev/pts补丁集我有一个想法:

sudo fuser -v /dev/ptmx

我想我通常使用什么将进程与 a 关联起来mount果然:

                     USER        PID ACCESS COMMAND
/dev/ptmx:           root      410   F.... kmscon
                     mikeserv  710   F.... terminology

因此,有了这些信息,我可以做的事情,例如terminology

sudo sh -c '${cmd:=grep rchar /proc/410/io} && printf 1 >/dev/pts/0 && $cmd'
###OUTPUT###
rchar: 667991010
rchar: 667991011

正如您所看到的,通过一些显式测试,可以使这样的过程非常可靠地输出任意 pty 的主进程。关于套接字,我相当确定可以从那个方向接近它,而socat不是使用调试器,但我还没有弄清楚如何实现。不过,我怀疑ss如果你比我更熟悉它可能会有所帮助:

sudo sh -c 'ss -oep | grep "$(printf "pid=%s\n" $(fuser /dev/ptmx))"'

因此,我实际上进行了一些更明确的测试来设置它:

sudo sh <<\CMD
    chkio() {
        read io io <$1
        dd bs=1 count=$$ </dev/zero >$2 2>/dev/null
        return $((($(read io io <$1; echo $io)-io)!=$$))
    }
    for pts in /dev/pts/[0-9]* ; do
        for ptm in $(fuser /dev/ptmx 2>/dev/null)
            do chkio /proc/$ptm/io $pts && break
        done && set -- "$@" "$ptm owns $pts"
    done
    printf %s\\n "$@"
 CMD

它向每个 pty 打印$$num 个\0空字节,并根据之前的检查检查每个主进程的 io。如果不同,$$则它将 pid 与 pty 相关联。这大多作品。我的意思是,对我来说,它返回:

410 owns /dev/pts/0
410 owns /dev/pts/1
710 owns /dev/pts/2

这是正确的,但是,显然,它有点活泼。我的意思是,如果其中一个当时正在读取一堆数据,它可能会错过。我正在尝试找出如何更改stty另一个 pty 上的模式,以便首先发送停止位或类似的内容,以便我可以修复该问题。

答案2

2017 年,Linux 获得了一项新功能,可以稍微简化此过程(commitd01c3289e7d,适用于 Linux 4.14 及更高版本)

获取打开的进程列表后/dev/ptmx

$ fuser dev/ptmx
/dev/ptmx:           1330334 1507443

pts可以这样接收号码:

for pid in $(fuser /dev/ptmx 2>/dev/null); do grep -r tty-index /proc/$pid/fdinfo; done
/proc/1330334/fdinfo/13:tty-index:  0
/proc/1330334/fdinfo/14:tty-index:  1
/proc/1330334/fdinfo/27:tty-index:  2
/proc/1330334/fdinfo/28:tty-index:  4
/proc/1507443/fdinfo/3:tty-index:   3

结果是从 a<pid>:<ptmx fd>到相应的映射/dev/pts/<index>

从4.90版本开始lsof可以使用该 API 来报告另一端/dev/ptmx并使用//dev/pts/x打开文件:-E+E

$ lsof -E -ad 0 -p $$
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
zsh     14335     user    0u   CHR  136,8      0t0   11 /dev/pts/8 14333,xterm,5u
$ lsof +E -ad 0 -p $$
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
xterm   14333     user    5u   CHR    5,2      0t0   87 /dev/ptmx ->/dev/pts/8 14335,zsh,0u 14335,zsh,1u 14335,zsh,2u 14335,zsh,10u 14391,lsof,0u 14391,lsof,1u 14391,lsof,2u
zsh     14335     user    0u   CHR  136,8      0t0   11 /dev/pts/8 14333,xterm,5u

答案3

如果您只是查找谁拥有该连接以及他们从何处连接,则WHO命令会很好地工作。

$ who
falsenames   tty8         Jun 13 16:54 (:0)
falsenames   pts/0        Jun 16 11:18 (:0)
falsenames   pts/1        Jun 16 12:59 (:0)
falsenames   pts/2        Jun 16 13:46 (:0)
falsenames   pts/3        Jun 16 14:10 (:0)
falsenames   pts/4        Jun 16 16:41 (:0)

如果您还想知道该连接上正在监听什么,w最后会显示这一点。

$ w
 16:44:09 up 2 days, 23:51,  6 users,  load average: 0.26, 0.98, 1.25
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
falsenames   tty8     :0               Fri16    2days 53:36   0.59s x-session-manager
falsenames   pts/0    :0               11:18    5:25m  1:10   1:10  synergys -a 10.23.8.245 -c .synergy.conf -f -d DEBUG
falsenames   pts/1    :0               12:59    3:44m  0.05s  0.05s bash
falsenames   pts/2    :0               13:46    2:52m  0.11s  0.11s bash
falsenames   pts/3    :0               14:10    2:17   0.07s  0.07s bash
falsenames   pts/4    :0               16:41    1.00s  0.04s  0.00s w

要获取 pid,请将 ps 限制为您正在查看的 tty 会话。启动时完全不引人注目。

$ ps -t pts/0 --forest 
  PID TTY          TIME CMD
23808 pts/0    00:00:00 bash
23902 pts/0    00:03:27  \_ synergys

请注意,这可能会导致转移注意力,具体取决于时机。但这是一个很好的起点。

$ tty
/dev/pts/4
$ ps -t pts/4 --forest
  PID TTY          TIME CMD
27479 pts/4    00:00:00 bash
 3232 pts/4    00:00:00  \_ ps
27634 pts/4    00:00:00 dbus-launch

答案4

我对qemu也有同样的问题,最后我找到了一个非常糟糕的解决方案(但仍然是一个解决方案):解析进程内存。

这是在这里起作用的,因为我知道 qemu 将远程 pts 存储在具有特定格式的字符串中并分配在堆上。也许它也可以在其他情况下工作,只需进行一些更改并重用来自 fusionr 输出的 pid(检查其他答案)。

代码改编自这里

#! /usr/bin/env python

import sys
pid = sys.argv[1]

import re
maps_file = open("/proc/" + pid + "/maps", 'r')
mem_file = open("/proc/" + pid + "/mem", 'r', 0)
for line in maps_file.readlines():
    # You may want to remove the 'heap' part to search all RAM
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r]).*\[heap\]', line)
    if m and m.group(3) == 'r':
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)
        chunk = mem_file.read(end - start)
        # You may want to adapt this one to reduce false matches
        idx = chunk.find("/dev/pts/")
        if idx != -1:
            end = chunk.find("\0", idx)
            print chunk[idx:end]
maps_file.close()
mem_file.close()

相关内容