// stapdyn utility functions // Copyright (C) 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 "config.h" #include #include #include extern "C" { #include #include #include } #ifdef HAVE_SELINUX #include #endif #include "dynutil.h" #include "../util.h" using namespace std; // Callback for dl_iterate_phdr to look for libdyninstAPI.so static int guess_dyninst_rt_callback(struct dl_phdr_info *info, size_t size __attribute__ ((unused)), void *data) { string& libdyninstAPI = *static_cast(data); const string name = info->dlpi_name ?: "(null)"; if (name.find("libdyninstAPI.so") != string::npos) libdyninstAPI = name; return 0; } // Look for libdyninstAPI.so in our own process, and use that // to guess the path for libdyninstAPI_RT.so static const string guess_dyninst_rt(void) { string libdyninstAPI; dl_iterate_phdr(guess_dyninst_rt_callback, &libdyninstAPI); string libdyninstAPI_RT; size_t so = libdyninstAPI.rfind(".so"); if (so != string::npos) { libdyninstAPI_RT = libdyninstAPI; libdyninstAPI_RT.insert(so, "_RT"); } return libdyninstAPI_RT; } // Check that environment DYNINSTAPI_RT_LIB exists and is a valid file. // If not, try to guess a good value and set it. bool check_dyninst_rt(void) { static const char rt_env_name[] = "DYNINSTAPI_RT_LIB"; static const char dyn_rw_env_name[] = "DYNINST_REWRITER_PATHS"; char* rt_env = getenv(rt_env_name); if (rt_env) { if (file_exists(rt_env)) { if (appendenv(dyn_rw_env_name, rt_env) != 0) { int olderrno = errno; staperror() << "Can't set " << dyn_rw_env_name << ": " << strerror(olderrno); return false; } return true; } staperror() << "Invalid " << rt_env_name << ": \"" << rt_env << "\"" << endl; } const string rt = guess_dyninst_rt(); if (rt.empty() || !file_exists(rt)) { staperror() << "Can't find libdyninstAPI_RT.so; try setting " << rt_env_name << endl; return false; } if (appendenv(dyn_rw_env_name, rt) != 0) { int olderrno = errno; staperror() << "Can't set " << rt_env_name << ": " << strerror(olderrno); return false; } return true; } // Check that SELinux settings are ok for Dyninst operation. bool check_dyninst_sebools(bool attach) { #ifdef HAVE_SELINUX // For all these checks, we could examine errno on failure to act differently // for e.g. ENOENT vs. EPERM. But since these are just early diagnostices, // I'm only going worry about successful bools for now. // deny_ptrace is definitely a blocker for us to attach at all if (security_get_boolean_active("deny_ptrace") > 0) { staperror() << "SELinux boolean 'deny_ptrace' is active, " "which blocks Dyninst" << endl; return false; } // We might have to get more nuanced about allow_execstack, especially if // Dyninst is later enhanced to work around this restriction. But for now, // this is also a blocker. if (security_get_boolean_active("allow_execstack") == 0) { staperror() << "SELinux boolean 'allow_execstack' is disabled, " "which blocks Dyninst" << endl; return false; } // In process-attach mode, SELinux will trigger "avc: denied { execmod }" // on ld.so, when the mutator is injecting the dlopen for libdyninstAPI_RT.so. if (attach && security_get_boolean_active("allow_execmod") == 0) { staperror() << "SELinux boolean 'allow_execmod' is disabled, " "which blocks Dyninst" << endl; return false; } #else (void)attach; // unused #endif return true; } // Check whether a process exited cleanly bool check_dyninst_exit(BPatch_process *process) { int code; switch (process->terminationStatus()) { case ExitedNormally: code = process->getExitCode(); if (code == EXIT_SUCCESS) return true; stapwarn() << "Child process exited with status " << code << endl; return false; case ExitedViaSignal: code = process->getExitSignal(); stapwarn() << "Child process exited with signal " << code << " (" << strsignal(code) << ")" << endl; return false; case NoExit: if (process->isTerminated()) stapwarn() << "Child process exited in an unknown manner" << endl; else stapwarn() << "Child process has not exited" << endl; return false; default: return false; } } // Get an untyped symbol from a dlopened module. // If flagged as 'required', throw an exception if missing or NULL. void * get_dlsym(void* handle, const char* symbol, bool required) { const char* err = dlerror(); // clear previous errors void *pointer = dlsym(handle, symbol); if (required) { if ((err = dlerror())) throw std::runtime_error("dlsym " + std::string(err)); if (pointer == NULL) throw std::runtime_error("dlsym " + std::string(symbol) + " is NULL"); } return pointer; } // // Logging, warnings, and errors, oh my! // // A null-sink output stream, similar to /dev/null // (no buffer -> badbit -> quietly suppressed output) static ostream nullstream(NULL); // verbosity, increased by -v unsigned stapdyn_log_level = 0; // Whether to suppress warnings, set by -w bool stapdyn_suppress_warnings = false; // Output file name, set by -o char *stapdyn_outfile_name = NULL; // Return a stream for logging at the given verbosity level. ostream& staplog(unsigned level) { if (level > stapdyn_log_level) return nullstream; return clog << program_invocation_short_name << ": "; } // Return a stream for warning messages. ostream& stapwarn(void) { if (stapdyn_suppress_warnings) return nullstream; return clog << program_invocation_short_name << ": " << colorize("WARNING:", "warning") << " "; } // Return a stream for error messages. ostream& staperror(void) { return clog << program_invocation_short_name << ": " << colorize("ERROR:", "error") << " "; } // Whether to color error and warning messages bool color_errors; // Initialized in main() // Adds coloring to strings std::string colorize(std::string str, std::string type) { if (str.empty() || !color_errors) return str; else { // Check if this type is defined in SYSTEMTAP_COLORS std::string color = parse_stap_color(type); if (!color.empty()) // no need to pollute terminal if not necessary return "\033[" + color + "m\033[K" + str + "\033[m\033[K"; else return str; } } /* Parse SYSTEMTAP_COLORS and returns the SGR parameter(s) for the given type. The env var SYSTEMTAP_COLORS must be in the following format: 'key1=val1:key2=val2:' etc... where valid keys are 'error', 'warning', 'source', 'caret', 'token' and valid values constitute SGR parameter(s). For example, the default setting would be: 'error=01;31:warning=00;33:source=00;34:caret=01:token=01' */ std::string parse_stap_color(std::string type) { const char *key, *col, *eq; int n = type.size(); int done = 0; key = getenv("SYSTEMTAP_COLORS"); if (key == NULL) key = "error=01;31:warning=00;33:source=00;34:caret=01:token=01"; else if (*key == '\0') return ""; // disable colors if set but empty while (!done) { if (!(col = strchr(key, ':'))) { col = strchr(key, '\0'); done = 1; } if (!((eq = strchr(key, '=')) && eq < col)) return ""; /* invalid syntax: no = in range */ if (!(key < eq && eq < col-1)) return ""; /* invalid syntax: key or val empty */ if (strspn(eq+1, "0123456789;") < (size_t)(col-eq-1)) return ""; /* invalid syntax: invalid char in val */ if (eq-key == n && type.compare(0, n, key, n) == 0) return string(eq+1, col-eq-1); if (!done) key = col+1; /* advance to next key */ } // Could not find the key return ""; } /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */