Commit 5a1d2e35 authored by Sasha Goldshtein's avatar Sasha Goldshtein

Support for global variable arguments in USDT probes

I previously thought it wouldn't be possible to probe arbitrary user-mode addresses,
but it turns out that it is. This was required to support global variable arguments
in USDT probes. For example, there are a few probes in libpthread whose arguments
refer to the mp_ global variable. These are now supported.

Additionally, refactored process-related utils into a ProcUtils class in procstat.py,
and implemented a version of `which` (`ProcUtils.which`) that is as portable as
possible -- it only relies on traversing the PATH environment variable.
parent ff9f231e
......@@ -26,7 +26,7 @@ import sys
basestring = (unicode if sys.version_info[0] < 3 else str)
from .libbcc import lib, _CB_TYPE
from .procstat import ProcStat
from .procstat import ProcStat, ProcUtils
from .table import Table
from .tracepoint import Perf, Tracepoint
from .usyms import ProcessSymbols
......
......@@ -31,3 +31,95 @@ class ProcStat(object):
return os.popen("cut -d' ' -f 22 /proc/%d/stat" %
self.pid).read()
class ProcUtils(object):
@staticmethod
def get_load_address(pid, bin_path):
"""
get_load_address(pid, bin_path)
Returns the address at which the specified module is loaded
in the specified process. The module path must match exactly
the file system path, not a symbolic link.
"""
with open("/proc/%d/maps" % pid) as m:
maps = m.readlines()
addrs = map(lambda l: l.split('-')[0],
filter(lambda l: bin_path in l, maps)
)
if len(addrs) == 0:
raise ValueError("lib %s not loaded in pid %d"
% (bin_path, pid))
return int(addrs[0], 16)
@staticmethod
def get_modules(pid):
"""
get_modules(pid)
Returns a list of all the modules loaded into the specified
process. Modules are enumerated by looking at /proc/$PID/maps
and returning the module name for regions that contain
executable code.
"""
with open("/proc/%d/maps" % pid) as f:
maps = f.readlines()
modules = []
for line in maps:
parts = line.strip().split()
if len(parts) < 6:
continue
if parts[5][0] == '[' or not 'x' in parts[1]:
continue
modules.append(parts[5])
return modules
@staticmethod
def is_shared_object(bin_path):
"""
is_shared_object(bin_path)
Returns whether the specified binary is a shared object, rather
than an executable. If it is neither, an error is raised.
"""
mime_type = os.popen("file --mime-type -b %s" % bin_path
).read().strip()
if mime_type == "application/x-sharedlib":
return True
if mime_type == "application/x-executable":
return False
raise ValueError("invalid mime type %s for binary %s" %
(mime_type, bin_path))
@staticmethod
def traverse_symlink(path):
"""Returns the actual path behind the specified symlink."""
return os.popen("readlink -f %s" % path).read().strip()
@staticmethod
def which(bin_path):
"""
which(bin_path)
Traverses the PATH environment variable, looking for the first
directory that contains an executable file named bin_path, and
returns the full path to that file, or None if no such file
can be found. This is meant to replace invocations of the
"which" shell utility, which doesn't have portable semantics
for skipping aliases.
"""
# Source: http://stackoverflow.com/a/377028
def is_exe(fpath):
return os.path.isfile(fpath) and \
os.access(fpath, os.X_OK)
fpath, fname = os.path.split(bin_path)
if fpath:
if is_exe(bin_path):
return bin_path
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, bin_path)
if is_exe(exe_file):
return exe_file
return None
This diff is collapsed.
......@@ -12,7 +12,7 @@
# Licensed under the Apache License, Version 2.0 (the "License")
# Copyright (C) 2016 Sasha Goldshtein.
from bcc import BPF, Tracepoint, Perf, USDTReader
from bcc import BPF, Tracepoint, Perf, ProcUtils, USDTReader
from time import sleep, strftime
import argparse
import re
......@@ -392,12 +392,9 @@ QUALIFIER int PROBENAME(struct pt_regs *ctx SIGNATURE)
def _attach_u(self):
libpath = BPF.find_library(self.library)
if libpath is None:
with os.popen(("which --skip-alias %s " +
"2>/dev/null") % self.library) as w:
libpath = w.read().strip()
libpath = ProcUtils.which(self.library)
if libpath is None or len(libpath) == 0:
self._bail("unable to find library %s" %
self.library)
self._bail("unable to find library %s" % self.library)
if self.probe_type == "u":
for i, location in enumerate(self.usdt.locations):
......
......@@ -331,7 +331,8 @@ BPF_PERF_OUTPUT(%s);
prefix = self.tp.generate_get_struct()
elif self.probe_type == "u":
signature += ", int __loc_id"
prefix = self.usdt.generate_usdt_cases()
prefix = self.usdt.generate_usdt_cases(
pid=Probe.pid if Probe.pid != -1 else None)
qualifier = "static inline"
data_fields = ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment