From 185a7d1d4860f5dcbe2fd5bfdd66b7341fecc7a2 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sat, 30 Nov 2019 15:28:44 +0100 Subject: add extrace-bpf, which uses bpftrace This is a ruby script that simulates extrace(1) getting the information on exec/exit from bpftrace. --- extrace-bpf | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100755 extrace-bpf diff --git a/extrace-bpf b/extrace-bpf new file mode 100755 index 0000000..4658cc7 --- /dev/null +++ b/extrace-bpf @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby + +require 'optparse' +require 'etc' + +# TODO: -p / cmd... (how?) + +BPF = <<'EOF' +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 +} -- cgit 1.4.1