stapdyn/mutator.cxx - systemtap
Global variables defined
Functions defined
Source code
// stapdyn mutator functions
// Copyright (C) 2012-2014 Red Hat Inc.
//
// This file is part of systemtap, and is free software. You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.
#include "mutator.h"
#include <algorithm>
extern "C" {
#include <dlfcn.h>
#include <wordexp.h>
#include <signal.h>
#include <time.h>
}
#include <BPatch_snippet.h>
#include "dynutil.h"
#include "../util.h"
extern "C" {
#include "../runtime/dyninst/stapdyn.h"
}
using namespace std;
// NB: since Dyninst callbacks have no context, we have to demux it
// to every mutator we've created, tracked by this vector.
static vector<mutator*> g_mutators;
static void
g_dynamic_library_callback(BPatch_thread *thread,
BPatch_module *module,
bool load)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->dynamic_library_callback(thread, module, load);
}
static void
g_post_fork_callback(BPatch_thread *parent, BPatch_thread *child)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->post_fork_callback(parent, child);
}
static void
g_exec_callback(BPatch_thread *thread)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->exec_callback(thread);
}
static void
g_exit_callback(BPatch_thread *thread, BPatch_exitType type)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->exit_callback(thread, type);
}
static void
g_thread_create_callback(BPatch_process *proc, BPatch_thread *thread)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->thread_create_callback(proc, thread);
}
static void
g_thread_destroy_callback(BPatch_process *proc, BPatch_thread *thread)
{
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->thread_destroy_callback(proc, thread);
}
static pthread_t g_main_thread = pthread_self();
static const sigset_t *g_signal_mask;
static void
g_signal_handler(int signal)
{
/* We only want the signal on our main thread, so it will interrupt the ppoll
* loop. If we get it on a different thread, just forward it. */
if (!pthread_equal(pthread_self(), g_main_thread))
{
pthread_kill(g_main_thread, signal);
return;
}
for (size_t i = 0; i < g_mutators.size(); ++i)
g_mutators[i]->signal_callback(signal);
}
__attribute__((constructor))
static void
setup_signals (void)
{
struct sigaction sa;
static sigset_t mask;
static const int signals[] = {
SIGHUP, SIGINT, SIGTERM, SIGQUIT,
};
/* Prepare the global sigmask for future use. */
sigemptyset (&mask);
for (size_t i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaddset (&mask, signals[i]);
g_signal_mask = &mask;
/* Prepare the common signal handler. */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = g_signal_handler;
sa.sa_flags = SA_RESTART;
sigemptyset (&sa.sa_mask);
for (size_t i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaddset (&sa.sa_mask, signals[i]);
/* Activate the handler for every signal. */
for (size_t i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaction (signals[i], &sa, NULL);
}
mutator::mutator (const string& module_name,
vector<string>& module_options):
module(NULL), module_name(resolve_path(module_name)),
modoptions(module_options), p_target_created(false),
p_target_error(false), utrace_enter_fn(NULL)
{
// NB: dlopen does a library-path search if the filename doesn't have any
// path components, which is why we use resolve_path(module_name)
sigemptyset(&signals_received);
g_mutators.push_back(this);
}
mutator::~mutator ()
{
// Explicitly drop our mutatee references, so we better
// control when their instrumentation is removed.
target_mutatee.reset();
mutatees.clear();
if (module)
{
dlclose(module);
module = NULL;
}
g_mutators.erase(find(g_mutators.begin(), g_mutators.end(), this));
}
// Do probes matching 'flag' exist?
bool
mutator::matching_probes_exist(uint64_t flag)
{
for (size_t i = 0; i < targets.size(); ++i)
{
for (size_t j = 0; j < targets[i].probes.size(); ++j)
{
if (targets[i].probes[j].flags & flag)
return true;
}
}
return false;
}
// Load the stap module and initialize all probe info.
bool
mutator::load ()
{
int rc;
// Open the module directly, so we can query probes or run simple ones.
(void)dlerror(); // clear previous errors
module = dlopen(module_name.c_str(), RTLD_NOW);
if (!module)
{
staperror() << "dlopen " << dlerror() << endl;
return false;
}
if ((rc = find_dynprobes(module, targets)))
return rc;
if (!targets.empty())
{
// Always watch for new libraries to probe.
patch.registerDynLibraryCallback(g_dynamic_library_callback);
// Always watch for new child processes, even if we don't have
// STAPDYN_PROBE_FLAG_PROC_BEGIN, because we might want to trigger
// any of the other types of probes in new processes too.
patch.registerPostForkCallback(g_post_fork_callback);
patch.registerExecCallback(g_exec_callback);
// Do we need a exit callback?
if (matching_probes_exist(STAPDYN_PROBE_FLAG_PROC_END))
patch.registerExitCallback(g_exit_callback);
// Do we need a thread create callback?
if (matching_probes_exist(STAPDYN_PROBE_FLAG_THREAD_BEGIN))
patch.registerThreadEventCallback(BPatch_threadCreateEvent,
g_thread_create_callback);
// Do we need a thread destroy callback?
if (matching_probes_exist(STAPDYN_PROBE_FLAG_THREAD_END))
patch.registerThreadEventCallback(BPatch_threadDestroyEvent,
g_thread_destroy_callback);
}
return true;
}
// Create a new process with the given command line
bool
mutator::create_process(const string& command)
{
if (target_mutatee)
{
staperror() << "Already attached to a target process!" << endl;
return false;
}
// Split the command into words. If wordexp can't do it,
// we'll just run via "sh -c" instead.
const char** child_argv;
const char* sh_argv[] = { "/bin/sh", "-c", command.c_str(), NULL };
wordexp_t words;
int rc = wordexp (command.c_str(), &words, WRDE_NOCMD|WRDE_UNDEF);
if (rc == 0)
child_argv = (/*cheater*/ const char**) words.we_wordv;
else if (rc == WRDE_BADCHAR)
child_argv = sh_argv;
else
{
staperror() << "wordexp parsing error (" << rc << ")" << endl;
return false;
}
// Search the PATH if necessary, then create the target process!
string fullpath = find_executable(child_argv[0]);
BPatch_process* app = patch.processCreate(fullpath.c_str(), child_argv);
if (!app)
{
staperror() << "Couldn't create the target process" << endl;
return false;
}
boost::shared_ptr<mutatee> m(new mutatee(app));
mutatees.push_back(m);
target_mutatee = m;
p_target_created = true;
if (!m->load_stap_dso(module_name))
return false;
if (!targets.empty())
m->instrument_dynprobes(targets);
return true;
}
// Attach to a specific existing process.
bool
mutator::attach_process(pid_t pid)
{
if (target_mutatee)
{
staperror() << "Already attached to a target process!" << endl;
return false;
}
BPatch_process* app = patch.processAttach(NULL, pid);
if (!app)
{
staperror() << "Couldn't attach to the target process" << endl;
return false;
}
boost::shared_ptr<mutatee> m(new mutatee(app));
mutatees.push_back(m);
target_mutatee = m;
p_target_created = false;
if (!m->load_stap_dso(module_name))
return false;
if (!targets.empty())
m->instrument_dynprobes(targets);
return true;
}
bool
mutator::init_modoptions()
{
typeof(&stp_global_setter) global_setter = NULL;
set_dlsym(global_setter, module, "stp_global_setter", false);
if (global_setter == NULL)
{
// Hypothetical backwards compatibility with older stapdyn:
stapwarn() << "Compiled module does not support -G globals" << endl;
return false;
}
for (vector<string>::iterator it = modoptions.begin();
it != modoptions.end(); it++)
{
string modoption = *it;
// Parse modoption as "name=value"
// XXX: compare whether this behaviour fits safety regex in buildrun.cxx
string::size_type separator = modoption.find('=');
if (separator == string::npos)
{
stapwarn() << "Could not parse module option '" << modoption << "'" << endl;
return false; // XXX: perhaps ignore the option instead?
}
string name = modoption.substr(0, separator);
string value = modoption.substr(separator+1);
int rc = global_setter(name.c_str(), value.c_str());
if (rc != 0)
{
stapwarn() << "Incorrect module option '" << modoption << "'" << endl;
return false; // XXX: perhaps ignore the option instead?
}
}
return true;
}
void
mutator::init_session_attributes()
{
typeof(&stp_global_setter) global_setter = NULL;
set_dlsym(global_setter, module, "stp_global_setter", false);
if (global_setter == NULL)
{
// Just return.
return;
}
// Note that the list of supported attributes should match with the
// list in 'struct _stp_sesion_attributes' in
// runtime/dyninst/session_attributes.h.
int rc = global_setter("@log_level", lex_cast(stapdyn_log_level).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'log_level' global" << endl;
rc = global_setter("@suppress_warnings",
lex_cast(stapdyn_suppress_warnings).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'suppress_warnings' global" << endl;
rc = global_setter("@stp_pid", lex_cast(getpid()).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'stp_pid' global" << endl;
if (target_mutatee)
{
rc = global_setter("@target", lex_cast(target_mutatee->process_id()).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'target' global" << endl;
}
size_t module_endpath = module_name.rfind('/');
size_t module_basename_start =
(module_endpath != string::npos) ? module_endpath + 1 : 0;
size_t module_basename_end = module_name.find('.', module_basename_start);
size_t module_basename_len = module_basename_end - module_basename_start;
string module_basename(module_name, module_basename_start, module_basename_len);
rc = global_setter("@module_name", module_basename.c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'module_name' global" << endl;
time_t now_t = time(NULL);
struct tm* now = localtime(&now_t);
if (now)
{
rc = global_setter("@tz_gmtoff", lex_cast(-now->tm_gmtoff).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'tz_gmtoff' global" << endl;
rc = global_setter("@tz_name", now->tm_zone);
if (rc != 0)
stapwarn() << "Couldn't set 'tz_name' global" << endl;
}
else
stapwarn() << "Couldn't discover local timezone info" << endl;
if (stapdyn_outfile_name)
{
rc = global_setter("@outfile_name",
lex_cast(stapdyn_outfile_name).c_str());
if (rc != 0)
stapwarn() << "Couldn't set 'outfile_name' global" << endl;
}
return;
}
// Initialize the module session
bool
mutator::run_module_init()
{
if (!module)
return false;
// First see if this is a shared-memory, multiprocess-capable module
typeof(&stp_dyninst_shm_init) shm_init = NULL;
typeof(&stp_dyninst_shm_connect) shm_connect = NULL;
set_dlsym(shm_init, module, "stp_dyninst_shm_init", false);
set_dlsym(shm_connect, module, "stp_dyninst_shm_connect", false);
if (shm_init && shm_connect)
{
// Initialize the shared-memory locally.
const char* shmem = shm_init();
if (shmem == NULL)
{
stapwarn() << "stp_dyninst_shm_init failed!" << endl;
return false;
}
module_shmem = shmem;
// After the session is initilized, then we'll map shmem in the target
}
else if (target_mutatee)
{
// For modules that don't support shared-memory, but still have a target
// process, we'll run init/exit in the target.
target_mutatee->call_function("stp_dyninst_session_init");
return true;
}
// From here, either this is a shared-memory module,
// or we have no target and thus run init directly anyway.
typeof(&stp_dyninst_session_init) session_init = NULL;
try
{
set_dlsym(session_init, module, "stp_dyninst_session_init");
}
catch (runtime_error& e)
{
staperror() << e.what() << endl;
return false;
}
// Before init runs, set any custom variables
if (!modoptions.empty() && !init_modoptions())
return false;
init_session_attributes();
int rc = session_init();
if (rc)
{
stapwarn() << "stp_dyninst_session_init returned " << rc << endl;
return false;
}
// Now we map the shared-memory into the target
if (target_mutatee && !module_shmem.empty())
{
vector<BPatch_snippet *> args;
args.push_back(new BPatch_constExpr(module_shmem.c_str()));
target_mutatee->call_function("stp_dyninst_shm_connect", args);
}
return true;
}
// Shutdown the module session
bool
mutator::run_module_exit()
{
if (!module)
return false;
if (target_mutatee && module_shmem.empty())
{
// For modules that don't support shared-memory, but still have a target
// process, we'll run init/exit in the target.
// XXX This may already have been done in its deconstructor if the process exited.
target_mutatee->call_function("stp_dyninst_session_exit");
return true;
}
// From here, either this is a shared-memory module,
// or we have no target and thus run exit directly anyway.
typeof(&stp_dyninst_session_exit) session_exit = NULL;
try
{
set_dlsym(session_exit, module, "stp_dyninst_session_exit");
}
catch (runtime_error& e)
{
staperror() << e.what() << endl;
return false;
}
session_exit();
return true;
}
// Check the status of all mutatees
bool
mutator::update_mutatees()
{
// We'll always break right away for SIGQUIT. We'll also break for any other
// signal if we didn't create the process. Otherwise, we should give the
// created process a chance to finish.
if (sigismember(&signals_received, SIGQUIT) ||
(!sigisemptyset(&signals_received) && !p_target_created))
return false;
if (target_mutatee && target_mutatee->is_terminated())
return false;
for (size_t i = 0; i < mutatees.size();)
{
boost::shared_ptr<mutatee> m = mutatees[i];
if (m != target_mutatee && m->is_terminated())
{
mutatees.erase(mutatees.begin() + i);
continue; // NB: without ++i
}
++i;
}
return true;
}
// Start the actual systemtap session!
bool
mutator::run ()
{
if (!targets.empty() && !target_mutatee)
stapwarn() << "process probes require a target (-c or -x)" << endl;
// Get the stap module ready...
run_module_init();
// And away we go!
if (target_mutatee)
{
// For our first event, fire the target's process.begin probes (if any)
target_mutatee->begin_callback();
target_mutatee->continue_execution();
// Dyninst's notification FD was fixed in 8.1; for earlier versions we'll
// fall back to the fully-blocking wait for now.
#ifdef DYNINST_8_1
// mask signals while we're preparing to poll
stap_sigmasker masked(g_signal_mask);
// Polling with a notification FD lets us wait on Dyninst while still
// letting signals break us out of the loop.
while (update_mutatees())
{
pollfd pfd;
pfd.fd = patch.getNotificationFD();
pfd.events = POLLIN;
pfd.revents = 0;
struct timespec timeout = { 10, 0 };
int rc = ppoll (&pfd, 1, &timeout, &masked.old);
if (rc < 0 && errno != EINTR)
break;
// Acknowledge and activate whatever events are waiting
patch.pollForStatusChange();
}
#else
while (update_mutatees())
patch.waitForStatusChange();
#endif
}
else // !target_mutatee
{
// With no mutatees, we just wait for a signal to exit.
stap_sigmasker masked(g_signal_mask);
while (sigisemptyset(&signals_received))
sigsuspend(&masked.old);
}
// Indicate failure if the target had anything but EXIT_SUCCESS
if (target_mutatee && target_mutatee->is_terminated())
p_target_error = !target_mutatee->check_exit();
// Detach from everything
target_mutatee.reset();
mutatees.clear();
// Shutdown the stap module.
return run_module_exit();
}
// Get the final exit status of this mutator
int mutator::exit_status ()
{
if (!module)
return EXIT_FAILURE;
// NB: Only shm modules are new enough to have stp_dyninst_exit_status at
// all, so we don't need to try in-target for old modules like session_exit.
typeof(&stp_dyninst_exit_status) get_exit_status = NULL;
set_dlsym(get_exit_status, module, "stp_dyninst_exit_status", false);
if (get_exit_status)
{
int status = get_exit_status();
if (status != EXIT_SUCCESS)
return status;
}
return p_target_error ? EXIT_FAILURE : EXIT_SUCCESS;
}
// Find a mutatee which matches the given process, else return NULL
boost::shared_ptr<mutatee>
mutator::find_mutatee(BPatch_process* process)
{
for (size_t i = 0; i < mutatees.size(); ++i)
if (*mutatees[i] == process)
return mutatees[i];
return boost::shared_ptr<mutatee>();
}
// Callback to respond to dynamically loaded libraries.
// Check if it matches our targets, and instrument accordingly.
void
mutator::dynamic_library_callback(BPatch_thread *thread,
BPatch_module *module,
bool load)
{
if (!load || !thread || !module)
return;
BPatch_process* process = thread->getProcess();
staplog(1) << "dlopen \"" << module->libraryName()
<< "\", pid = " << process->getPid() << endl;
boost::shared_ptr<mutatee> mut = find_mutatee(process);
if (mut)
mut->instrument_object_dynprobes(module->getObject(), targets);
}
// Callback to respond to post fork events. Check if it matches our
// targets, and handle accordingly.
void
mutator::post_fork_callback(BPatch_thread *parent, BPatch_thread *child)
{
if (!child || !parent)
return;
BPatch_process* child_process = child->getProcess();
BPatch_process* parent_process = parent->getProcess();
staplog(1) << "post fork, parent " << parent_process->getPid()
<< ", child " << child_process->getPid() << endl;
boost::shared_ptr<mutatee> mut = find_mutatee(parent_process);
if (mut)
{
// Clone the mutatee for the new process.
boost::shared_ptr<mutatee> m(new mutatee(child_process));
mutatees.push_back(m);
m->copy_forked_instrumentation(*mut);
// Trigger any process.begin probes.
m->begin_callback(child);
}
}
// Callback to respond to exec events. Check if it matches our
// targets, and handle accordingly.
void
mutator::exec_callback(BPatch_thread *thread)
{
if (!thread)
return;
BPatch_process* process = thread->getProcess();
staplog(1) << "exec, pid = " << process->getPid() << endl;
boost::shared_ptr<mutatee> mut = find_mutatee(process);
if (mut)
{
// Clear previous instrumentation
mut->exec_reset_instrumentation();
// NB: Until Dyninst commit 2b6c10ac15dc (in 8.2), loadLibrary in a
// fork-execed would hang waiting for a stopped process to continue.
#ifdef DYNINST_8_2
// Load our module again in the new process
if (mut->load_stap_dso(module_name))
{
if (!targets.empty())
mut->instrument_dynprobes(targets);
// Now we map the shared-memory into the target
if (!module_shmem.empty())
{
vector<BPatch_snippet *> args;
args.push_back(new BPatch_constExpr(module_shmem.c_str()));
mut->call_function("stp_dyninst_shm_connect", args);
}
// Trigger any process.end probes for the pre-exec process.
mut->exit_callback(thread, true);
// Trigger any process.begin probes.
mut->begin_callback(thread);
}
#endif
}
}
void
mutator::exit_callback(BPatch_thread *thread,
BPatch_exitType type __attribute__((unused)))
{
if (!thread)
return;
// 'thread' is the thread that requested the exit, not necessarily the
// main thread.
BPatch_process* process = thread->getProcess();
int pid = process->getPid();
staplog(1) << "exit callback, pid = " << pid << endl;
boost::shared_ptr<mutatee> mut = find_mutatee(process);
if (mut)
{
// FIXME: We'd like to call the mutatee's exit_callback()
// function, but we've got a problem. The mutatee can't stop the
// process to call the exit probe within the target (it finishes
// exiting before we can). So, we'll call the probe(s) locally
// here. This works, but the context is wrong (the mutator, not
// the mutatee).
const vector<dynprobe_location>& proc_end_probes =
mut->find_attached_probes(STAPDYN_PROBE_FLAG_PROC_END);
if (proc_end_probes.empty())
return;
if (utrace_enter_fn == NULL)
try
{
set_dlsym(utrace_enter_fn, module, "enter_dyninst_utrace_probe");
}
catch (runtime_error& e)
{
staperror() << e.what() << endl;
return;
}
staplog(2) << "firing " << proc_end_probes.size()
<< " process.end probes in the mutator for pid "
<< pid << endl;
for (size_t p = 0; p < proc_end_probes.size(); ++p)
{
const dynprobe_location& probe = proc_end_probes[p];
staplog(3) << "calling utrace function in the mutator for pid "
<< pid << ", probe index " << probe.index << endl;
int rc = utrace_enter_fn(probe.index, NULL);
if (rc)
stapwarn() << "enter_dyninst_utrace_probe returned "
<< rc << endl;
}
}
}
void
mutator::thread_create_callback(BPatch_process *proc, BPatch_thread *thread)
{
if (!proc || !thread)
return;
boost::shared_ptr<mutatee> mut = find_mutatee(proc);
if (mut)
mut->thread_callback(thread, true);
}
void
mutator::thread_destroy_callback(BPatch_process *proc, BPatch_thread *thread)
{
if (!proc || !thread)
return;
boost::shared_ptr<mutatee> mut = find_mutatee(proc);
if (mut)
mut->thread_callback(thread, false);
}
// Callback to respond to signals.
void
mutator::signal_callback(int signal)
{
sigaddset(&signals_received, signal);
}
/* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */