// Copyright (C) Andrew Tridgell 2002 (original file) // Copyright (C) 2006-2008 Red Hat Inc. (systemtap changes) // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "config.h" #include "session.h" #include "hash.h" #include "util.h" #include #include #include #include extern "C" { #include #include #include } using namespace std; void hash::start() { mdfour_begin(&md4); } void hash::add(const unsigned char *buffer, size_t size) { parm_stream << "," << buffer; mdfour_update(&md4, buffer, size); } void hash::add_file(const std::string& filename) { struct stat st; if (stat(filename.c_str(), &st) != 0) st.st_size = st.st_mtime = -1; add(filename); add(st.st_size); add(st.st_mtime); } string hash::get_parms() { string parms_str = parm_stream.str(); parm_stream.clear(); if (!parms_str.empty()) parms_str.erase(parms_str.begin()); // skip leading "," return parms_str; } void hash::result(string& r) { ostringstream rstream; unsigned char sum[16]; mdfour_update(&md4, NULL, 0); mdfour_result(&md4, sum); for (int i=0; i<16; i++) { rstream << hex << setfill('0') << setw(2) << (unsigned)sum[i]; } rstream << "_" << setw(0) << dec << (unsigned)md4.totalN; r = rstream.str(); } void create_hash_log(const string &type_str, const string &parms, const string &result, const string &hash_log_path) { ofstream log_file; time_t rawtime; time ( &rawtime ); string time_str(ctime (&rawtime)); log_file.open(hash_log_path.c_str()); log_file << "[" << time_str.substr(0,time_str.length()-1); // erase terminated '\n' log_file << "]" << type_str; log_file << ": " << parms << endl; log_file << "result:" << result << endl; log_file.close(); } static const hash& get_base_hash (systemtap_session& s) { if (s.base_hash) return *s.base_hash; s.base_hash = new hash(); hash& h = *s.base_hash; // Hash kernel release and arch. h.add(s.kernel_release); h.add(s.kernel_build_tree); h.add(s.architecture); // Hash a few kernel version/build-id files too // (useful for kernel developers reusing a single source tree) h.add_file(s.kernel_build_tree + "/.config"); h.add_file(s.kernel_build_tree + "/.version"); h.add_file(s.kernel_build_tree + "/include/linux/compile.h"); h.add_file(s.kernel_build_tree + "/include/linux/version.h"); h.add_file(s.kernel_build_tree + "/include/linux/utsrelease.h"); // If the kernel is a git working directory, then add the git HEAD // revision to our hash as well. // XXX avoiding this for now, because it's potentially expensive and has // uncertain gain. The only corner case that this may help is if a developer // is switching the source tree without rebuilding the kernel... ///h.add(git_revision(s.kernel_build_tree)); // Hash runtime path (that gets added in as "-R path"). h.add(s.runtime_path); // Hash compiler path, size, and mtime. We're just going to assume // we'll be using gcc. XXX: getting kbuild to spit out out would be // better. h.add_file(find_executable("gcc")); // Hash the systemtap size and mtime. We could use VERSION/DATE, // but when developing systemtap that doesn't work well (since you // can compile systemtap multiple times in 1 day). Since we don't // know exactly where we're getting run from, we'll use // /proc/self/exe. h.add_file("/proc/self/exe"); return h; } static bool create_hashdir (systemtap_session& s, const string& result, string& hashdir) { int nlevels = 1; // Use a N level subdir for the cache path to reduce the impact on // filesystems which are slow for large directories. Let N be adjustable. const char *s_n = getenv("SYSTEMTAP_NLEVELS"); if (s_n) { nlevels = atoi(s_n); if (nlevels < 1) nlevels = 1; if (nlevels > 8) nlevels = 8; } hashdir = s.cache_path; for (int i = 0; i < nlevels; i++) { hashdir += string("/") + result[i*2] + result[i*2 + 1]; if (create_dir(hashdir.c_str()) != 0) { if (! s.suppress_warnings) cerr << "Warning: failed to create cache directory (\"" << hashdir + "\"): " << strerror(errno) << ", disabling cache support." << endl; s.use_cache = s.use_script_cache = false; return false; } } return true; } void find_script_hash (systemtap_session& s, const string& script) { hash h(get_base_hash(s)); struct stat st; // Hash getuid. This really shouldn't be necessary (since who you // are doesn't change the generated output), but the hash gets used // as the module name. If two different users try to run the same // script at the same time, we need something to differentiate the // module name. h.add(getuid()); // Hash user-specified arguments (that change the generated module). h.add(s.bulk_mode); // '-b' h.add(s.timing); // '-t' h.add(s.prologue_searching); // '-P' h.add(s.ignore_vmlinux); // --ignore-vmlinux h.add(s.ignore_dwarf); // --ignore-dwarf h.add(s.consult_symtab); // --kelf, --kmap h.add(s.skip_badvars); // --skip-badvars h.add(s.unprivileged); // --unprivileged h.add(s.omit_werror); // undocumented, evil if (!s.kernel_symtab_path.empty()) // --kmap { h.add(s.kernel_symtab_path); if (stat(s.kernel_symtab_path.c_str(), &st) == 0) { // NB: stat of /proc/kallsyms always returns size=0, mtime=now... // which is a good reason to use the default /boot/System.map-2.6.xx // instead. h.add(st.st_size); h.add(st.st_mtime); } } for (unsigned i = 0; i < s.macros.size(); i++) h.add(s.macros[i]); // Add any custom kbuild flags (-B) for (unsigned i = 0; i < s.kbuildflags.size(); i++) h.add(s.kbuildflags[i]); // -d MODULE for (set::iterator it = s.unwindsym_modules.begin(); it != s.unwindsym_modules.end(); it++) h.add(*it); // XXX: a build-id of each module might be even better // Add in pass 2 script output. h.add(script); // Get the directory path to store our cached script string result, hashdir; h.result(result); if (!create_hashdir(s, result, hashdir)) return; // Update module name to be 'stap_{hash start}'. '{hash start}' // must not be too long. This shouldn't happen, since the maximum // size of a hash is 32 fixed chars + 1 (for the '_') + a max of 11. s.module_name = "stap_" + result; if (s.module_name.size() >= (MODULE_NAME_LEN - 1)) s.module_name.resize(MODULE_NAME_LEN - 1); // 'ccache' would use a hash path of something like: // s.hash_path = hashdir + "/" + result.substr(nlevels); // which would look like: // ~/.stap_cache/A/B/CDEFGHIJKLMNOPQRSTUVWXYZABCDEF_XXX // // We're using the following so that the module can be used straight // from the cache if desired. This ends up looking like this: // ~/.stap_cache/A/B/stap_ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEF_XXX.ko s.hash_path = hashdir + "/" + s.module_name + ".ko"; // Update C source name with new module_name. s.translated_source = string(s.tmpdir) + "/" + s.module_name + ".c"; create_hash_log(string("script_hash"), h.get_parms(), result, hashdir + "/" + s.module_name + "_hash.log"); } void find_stapconf_hash (systemtap_session& s) { hash h(get_base_hash(s)); // Add any custom kbuild flags for (unsigned i = 0; i < s.kbuildflags.size(); i++) h.add(s.kbuildflags[i]); // Get the directory path to store our cached stapconf parameters string result, hashdir; h.result(result); if (!create_hashdir(s, result, hashdir)) return; s.stapconf_name = "stapconf_" + result + ".h"; s.stapconf_path = hashdir + "/" + s.stapconf_name; create_hash_log(string("stapconf_hash"), h.get_parms(), result, hashdir + "/stapconf_" + result + "_hash.log"); } string find_tracequery_hash (systemtap_session& s, const vector& headers) { hash h(get_base_hash(s)); // Add the tracepoint headers to the computed hash for (size_t i = 0; i < headers.size(); ++i) h.add_file(headers[i]); // Add any custom kbuild flags for (unsigned i = 0; i < s.kbuildflags.size(); i++) h.add(s.kbuildflags[i]); // Get the directory path to store our cached module string result, hashdir; h.result(result); if (!create_hashdir(s, result, hashdir)) return ""; create_hash_log(string("tracequery_hash"), h.get_parms(), result, hashdir + "/tracequery_" + result + "_hash.log"); return hashdir + "/tracequery_" + result + ".ko"; } string find_typequery_hash (systemtap_session& s, const string& name) { hash h(get_base_hash(s)); // Add the typequery name to distinguish the hash h.add(name); if (name[0] == 'k') // Add any custom kbuild flags for (unsigned i = 0; i < s.kbuildflags.size(); i++) h.add(s.kbuildflags[i]); // Get the directory path to store our cached module string result, hashdir; h.result(result); if (!create_hashdir(s, result, hashdir)) return ""; create_hash_log(string("typequery_hash"), h.get_parms(), result, hashdir + "/typequery_" + result + "_hash.log"); return hashdir + "/typequery_" + result + (name[0] == 'k' ? ".ko" : ".so"); } /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */