about summary refs log tree commit diff
path: root/extrace-bpf
blob: d72f84709436bef1307fcd7b57beb35f7b9fddcb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env ruby

require 'optparse'
require 'etc'

# TODO: -p / cmd... (how?)

BPF = <<'EOF'
BEGIN {
  printf("SEP2\n");
}
tracepoint:syscalls:sys_enter_execve {
  printf("%ld +%d %d", elapsed, pid, uid);
  join(args->argv, "SEP1");
  printf("SEP2\n");
}
tracepoint:syscalls:sys_enter_exit_group {
  printf("%ld -%d %d\nSEP2\n", elapsed, pid, args->error_code);
}
EOF

SEP1 = "\xff\xff\xff".b
SEP2 = "\xff\xfe\xff".b

Procc = Struct.new(:pid, :uid, :name, :args, :start_time, :ppid, :depth)

PIDS = {}

def pid_depth(pid)
  procc = PIDS[pid]
  if procc && procc.depth
    procc.depth
  else
    procc = (PIDS[pid] ||= Procc.new(pid))
    stat = File.read("/proc/#{pid}/stat").b
    if stat =~ /.*\) . (\d+)/
      ppid = $1.to_i
      if ppid == 0
        return 0
      else
        procc.ppid = ppid
        procc.depth = pid_depth(ppid) + 1
      end
    end
  end
rescue Errno::ENOENT, Errno::ESRCH => e
  puts "failed to get pid #{pid} info: #{e}"
  0
end

def fmtcmd(c)
  if c.empty? || c =~ /[\001-\040`^#*\[\]|\\?${}()'\"<>&;\177]/
    "'" + c.gsub("'", "'\\''").gsub("\n", "'$'\\n''") + "'"
  else
    c
  end
end

def fmtcmds(cmds)
  cmds.map { |cmd| fmtcmd(cmd) }.join(' ')
end

params = ARGV.getopts("deflqtu")
dflag = params['d']
eflag = params['e']
fflag = params['f']
lflag = params['l']
qflag = params['q']
tflag = params['t']
uflag = params['u']

IO.popen(["bpftrace", "-e", BPF.gsub("SEP1", SEP1).gsub("SEP2", SEP2)],
         "r:BINARY") { |output|
  while line = output.gets("\n#{SEP2}\n")
    line.chomp!("\n#{SEP2}\n")
    case line
    when /\A(\d+) \+(\d+) (\d+)/
      elapsed = $1.to_i
      pid = $2.to_i
      uid = $3.to_i
      comm = $'.split(SEP1)
      
      next  unless comm.first

      PIDS[pid] = Procc.new(pid, uid, comm.first, comm[1..-1], elapsed)

      print "  " * pid_depth(pid)  unless fflag
      print pid
      print "+"  if tflag
      print " "
      print "<#{Etc.getpwuid(uid).name}> "  if uflag
      if dflag
        print (File.readlink("/proc/#{pid}/cwd") rescue "-")
        print " % "
      end
      comm[0] = (File.readlink("/proc/#{pid}/exe") rescue comm[0])  if lflag
      if qflag
        print fmtcmd(comm[0])
      else
        print fmtcmds(comm)
      end
      if eflag
        print "   "
        print (File.read("/proc/#{pid}/environ") rescue "-").
                b.split("\x00").map { |e|
          if e =~ /\A[a-zA-Z0-9_]+=/
            $& + fmtcmd($')
          else
            fmtcmd(e)
          end
        }.join(" ")
      end
      puts
    when /\A(\d+) -(\d+) (\d+)/
      elapsed = $1.to_i
      pid = $2.to_i
      status = $3.to_i

      if procc = PIDS[pid]
        PIDS.delete(pid)
        next  unless procc.name
        if tflag
          dur = (elapsed - procc.start_time) / 1e9
          print "  " * pid_depth(pid)  unless fflag
          puts "#{pid}- #{procc.name} exited status=#{status} time=#{"%.3f" % dur}s"
        end
      end
    when /^Attaching/
      # ignore
    else
      warn "can't parse #{line}"
    end
  end
}