blob: b576b7ae56b44689b0adfed0cb9f4cbd07dcf51b [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Brendan Gregg6049d3f2016-10-16 12:33:50 -07002# @lint-avoid-python-3-compatibility-imports
3#
4# ttysnoop Watch live output from a tty or pts device.
5# For Linux, uses BCC, eBPF. Embedded C.
6#
7# Due to a limited buffer size (see BUFSIZE), some commands (eg, a vim
8# session) are likely to be printed a little messed up.
9#
10# Copyright (c) 2016 Brendan Gregg.
11# Licensed under the Apache License, Version 2.0 (the "License")
12#
13# Idea: from ttywatcher.
14#
15# 15-Oct-2016 Brendan Gregg Created this.
16
17from __future__ import print_function
18from bcc import BPF
Brendan Gregg6049d3f2016-10-16 12:33:50 -070019from subprocess import call
20import argparse
21from sys import argv
22import sys
23from os import stat
24
25def usage():
26 print("USAGE: %s [-Ch] {PTS | /dev/ttydev} # try -h for help" % argv[0])
27 exit()
28
29# arguments
30examples = """examples:
31 ./ttysnoop /dev/pts/2 # snoop output from /dev/pts/2
32 ./ttysnoop 2 # snoop output from /dev/pts/2 (shortcut)
33 ./ttysnoop /dev/console # snoop output from the system console
34 ./ttysnoop /dev/tty0 # snoop output from /dev/tty0
35"""
36parser = argparse.ArgumentParser(
37 description="Snoop output from a pts or tty device, eg, a shell",
38 formatter_class=argparse.RawDescriptionHelpFormatter,
39 epilog=examples)
40parser.add_argument("-C", "--noclear", action="store_true",
41 help="don't clear the screen")
42parser.add_argument("device", default="-1",
43 help="path to a tty device (eg, /dev/tty0) or pts number")
Nathan Scottcf0792f2018-02-02 16:56:50 +110044parser.add_argument("--ebpf", action="store_true",
45 help=argparse.SUPPRESS)
Brendan Gregg6049d3f2016-10-16 12:33:50 -070046args = parser.parse_args()
47debug = 0
48
49if args.device == "-1":
50 usage()
51
52path = args.device
53if path.find('/') != 0:
54 path = "/dev/pts/" + path
55try:
56 pi = stat(path)
57except:
58 print("Unable to read device %s. Exiting." % path)
59 exit()
60
61# define BPF program
62bpf_text = """
63#include <uapi/linux/ptrace.h>
64#include <linux/fs.h>
65
66#define BUFSIZE 256
67struct data_t {
68 int count;
69 char buf[BUFSIZE];
70};
71
72BPF_PERF_OUTPUT(events);
73
74int kprobe__tty_write(struct pt_regs *ctx, struct file *file,
75 const char __user *buf, size_t count)
76{
77 if (file->f_inode->i_ino != PTS)
78 return 0;
79
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -050080 // bpf_probe_read_user() can only use a fixed size, so truncate to count
Brendan Gregg6049d3f2016-10-16 12:33:50 -070081 // in user space:
82 struct data_t data = {};
Sumanth Korikkar264b2cc2020-04-07 18:30:44 +020083 bpf_probe_read_user(&data.buf, BUFSIZE, (void *)buf);
Brendan Gregg6049d3f2016-10-16 12:33:50 -070084 if (count > BUFSIZE)
85 data.count = BUFSIZE;
86 else
87 data.count = count;
88 events.perf_submit(ctx, &data, sizeof(data));
89
90 return 0;
91};
92"""
93
94bpf_text = bpf_text.replace('PTS', str(pi.st_ino))
Nathan Scottcf0792f2018-02-02 16:56:50 +110095if debug or args.ebpf:
Brendan Gregg6049d3f2016-10-16 12:33:50 -070096 print(bpf_text)
Nathan Scottcf0792f2018-02-02 16:56:50 +110097 if args.ebpf:
98 exit()
Brendan Gregg6049d3f2016-10-16 12:33:50 -070099
100# initialize BPF
101b = BPF(text=bpf_text)
102
Brendan Gregg6049d3f2016-10-16 12:33:50 -0700103if not args.noclear:
104 call("clear")
105
106# process event
107def print_event(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800108 event = b["events"].event(data)
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200109 print("%s" % event.buf[0:event.count].decode('utf-8', 'replace'), end="")
Brendan Gregg6049d3f2016-10-16 12:33:50 -0700110 sys.stdout.flush()
111
112# loop with callback to print_event
113b["events"].open_perf_buffer(print_event)
114while 1:
Jerome Marchand51671272018-12-19 01:57:24 +0100115 try:
116 b.perf_buffer_poll()
117 except KeyboardInterrupt:
118 exit()