// Setup routines for creating fully populated DWFLs. Used in pass 2 and 3. // Copyright (C) 2009-2018 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 "setupdwfl.h" #include "dwarf_wrappers.h" #include "dwflpp.h" #include "session.h" #include "util.h" #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include #include #include } // XXX: also consider adding $HOME/.debug/ for perf build-id-cache static const char *debuginfo_path_arr = "+:.debug:/usr/lib/debug:/var/cache/abrt-di/usr/lib/debug:build"; static const char *debuginfo_env_arr = getenv("SYSTEMTAP_DEBUGINFO_PATH"); static char *debuginfo_path = (char *)(debuginfo_env_arr ?: debuginfo_path_arr); // NB: kernel_build_tree doesn't enter into this, as it's for // kernel-side modules only. // XXX: also consider adding $HOME/.debug/ for perf build-id-cache static const char *debuginfo_usr_path_arr = "+:.debug:/usr/lib/debug:/var/cache/abrt-di/usr/lib/debug"; static char *debuginfo_usr_path = (char *)(debuginfo_env_arr ?: debuginfo_usr_path_arr); // A pointer to the current systemtap session for use only by a few // dwfl calls. DO NOT rely on this, as it is cleared after use. // This is a kludge. static systemtap_session* current_session_for_find_debuginfo; static const Dwfl_Callbacks kernel_callbacks = { dwfl_linux_kernel_find_elf, internal_find_debuginfo, dwfl_offline_section_address, (char **) & debuginfo_path }; static const Dwfl_Callbacks user_callbacks = { NULL, internal_find_debuginfo, NULL, /* ET_REL not supported for user space, only ET_EXEC and ET_DYN. dwfl_offline_section_address, */ (char **) & debuginfo_usr_path }; using namespace std; // Setup in setup_dwfl_kernel(), for use in setup_dwfl_report_kernel_p(). // Either offline_search_modname or offline_search_names is // used. When offline_search_modname is not NULL then // offline_search_names is ignored. static const char *offline_search_modname; static set offline_search_names; static unsigned offline_modules_found; // Whether or not we are done reporting kernel modules in // set_dwfl_report_kernel_p(). static bool setup_dwfl_done; // Determines whether or not we will make setup_dwfl_report_kernel_p // report true for all module dependencies. This is necessary for // correctly resolving some dwarf constructs that relocate against // symbols in vmlinux and/or other modules they depend on. See PR10678. static const bool setup_all_deps = true; // Where to find the kernel (and the Modules.dep file). Setup in // setup_dwfl_kernel(), used by dwfl_linux_kernel_report_offline() and // setup_mod_deps(). static string elfutils_kernel_path; static bool is_comma_dash(const char c) { return (c == ',' || c == '-'); } // The path to the abrt-action-install-debuginfo-to-abrt-cache program. static const string abrt_path = (access ("/usr/bin/abrt-action-install-debuginfo-to-abrt-cache", X_OK) == 0 ? "/usr/bin/abrt-action-install-debuginfo-to-abrt-cache" : (access ("/usr/libexec/abrt-action-install-debuginfo-to-abrt-cache", X_OK) == 0 ? "/usr/libexec/abrt-action-install-debuginfo-to-abrt-cache" : "")); // The module name is the basename (without the extension) of the module path, // with ',' and '-' replaced by '_'. This is a (more or less safe) heuristic: // the actual name by which the module is known once inside the kernel is not // derived from the path, but from the .gnu.linkonce.this_module section of the // KO. In practice, modules in /lib/modules/ respect this convention, and we // require it as well for out-of-tree kernel modules. string modname_from_path(const string &path) { size_t slash = path.rfind('/'); if (slash == string::npos) return ""; string name = path.substr(slash + 1); // First look for .ko extension variants like ".ko" or ".ko.xz" // If that fails, look for any ".*" extension at all. size_t extension = name.rfind(".ko"); if (extension == string::npos) extension = name.rfind('.'); if (extension == string::npos) return ""; name.erase(extension); replace_if(name.begin(), name.end(), is_comma_dash, '_'); return name; } static bool offline_search_names_find(const string &modpath) { if (offline_search_names.find(modpath) != offline_search_names.end()) return 1; string modname = modname_from_path (modpath); return offline_search_names.find(modname) != offline_search_names.end(); } // Try to parse modules.dep file, // Simple format: module path (either full or relative), colon, // (possibly empty) space delimited list of module (path) // dependencies. static void setup_mod_deps() { string modulesdep; string kernel_path; ifstream in; string l; if (elfutils_kernel_path[0] == '/') { kernel_path = elfutils_kernel_path; } else { string sysroot = ""; if (current_session_for_find_debuginfo) sysroot = current_session_for_find_debuginfo->sysroot; kernel_path = sysroot + "/lib/modules/" + elfutils_kernel_path; } modulesdep = kernel_path + "/modules.dep"; in.open(modulesdep.c_str()); if (in.fail ()) return; while (getline (in, l)) { size_t off = l.find (':'); if (off != string::npos) { string modpath, modname; modpath = l.substr (0, off); modname = modname_from_path (modpath); if (modname == "") continue; if (modpath[0] != '/') modpath = kernel_path + "/" + modpath; bool dep_needed = 0; if (offline_search_modname != NULL) { if (dwflpp::name_has_wildcard (offline_search_modname)) { dep_needed = !fnmatch (offline_search_modname, modname.c_str (), 0); if (dep_needed) offline_search_names.insert (modpath); } else { dep_needed = ! strcmp(modname.c_str (), offline_search_modname); if (dep_needed) offline_search_names.insert (modpath); } } else if (offline_search_names.find(modpath) != offline_search_names.end()) dep_needed = 1; else { set::iterator it = offline_search_names.begin(); while (it != offline_search_names.end()) { string modname; modname = modname_from_path(modpath); if (*it == modname) { dep_needed = 1; offline_search_names.erase(it); offline_search_names.insert(modpath); break; } it++; } } if (! dep_needed) continue; string depstring = l.substr (off + 1); if (depstring.size () > 0) { stringstream ss (depstring); string deppath; while (ss >> deppath) offline_search_names.insert (deppath); } } } // We always want kernel (needed in list so size checks match). // Everything needed now stored in offline_search_names. offline_search_names.insert ("kernel"); offline_search_modname = NULL; } // Set up our offline search for kernel modules. We don't want the // offline search iteration to do a complete search of the kernel // build tree, since that's wasteful, so create a predicate that // filters and stops reporting as soon as we got everything. static int setup_dwfl_report_kernel_p(const char* modname, const char* filename) { assert_no_interrupts(); if (setup_dwfl_done) return -1; assert (current_session_for_find_debuginfo); if (current_session_for_find_debuginfo->verbose > 4) clog << _F("checking pattern '%s' vs. module '%s' file '%s'\n", offline_search_modname ?: "", modname ?: "", filename ?: ""); // elfutils sends us NULL filenames sometimes if it can't find dwarf if (filename == NULL) return 0; // Check kernel first since it is often the only thing needed, // then we never have to parse and setup the module deps map. // It will be reported as the very first thing. if (setup_all_deps && ! strcmp (modname, "kernel")) { if ((offline_search_modname != NULL && ! strcmp (offline_search_modname, "kernel")) || (offline_search_names.size() == 1 && *offline_search_names.begin() == "kernel")) setup_dwfl_done = true; else setup_mod_deps(); offline_modules_found++; return 1; } // If offline_search_modname is setup use it (either as regexp or // explicit module/kernel name) and ignore offline_search_names. // Otherwise use offline_search_names exclusively. if (offline_search_modname != NULL) { if (dwflpp::name_has_wildcard (offline_search_modname)) { // XXX: see also dwflpp::module_name_matches() int match_p = !fnmatch(offline_search_modname, modname, 0); // In the wildcard case, we don't short-circuit (return -1) // analogously to dwflpp::module_name_final_match(). if (match_p) offline_modules_found++; return match_p; } else { /* non-wildcard mode, reject mismatching module names */ if (strcmp(modname, offline_search_modname)) return 0; else { // Done, only one name needed and found it. offline_modules_found++; setup_dwfl_done = true; return 1; } } } else { /* find all in set mode, reject mismatching module names */ if (!offline_search_names_find(filename)) return 0; else { offline_modules_found++; if (offline_search_names.size() == offline_modules_found) setup_dwfl_done = true; return 1; } } } static char * path_insert_sysroot(string sysroot, string path) { char * path_new; size_t pos = 1; if (path[0] == '/') path.replace(0, 1, sysroot); while (true) { pos = path.find(":/", pos); if (pos == string::npos) break; path.replace(pos, 2, ":" + sysroot); ++pos; } path_new = new char[path.size()+1]; strcpy (path_new, path.c_str()); return path_new; } void debuginfo_path_insert_sysroot(string sysroot) { // FIXME: This is a short-term fix, until we expect sysroot paths to // always end with a '/' (and never be empty). // // The path_insert_sysroot() function assumes that sysroot has a '/' // on the end. Make sure that is true. if (! sysroot.empty() && *(sysroot.end() - 1) != '/') sysroot.append(1, '/'); debuginfo_path = path_insert_sysroot(sysroot, debuginfo_path); debuginfo_usr_path = path_insert_sysroot(sysroot, debuginfo_usr_path); } static Dwfl * setup_dwfl_kernel (unsigned *modules_found, systemtap_session &s) { Dwfl *dwfl = dwfl_begin (&kernel_callbacks); DWFL_ASSERT ("dwfl_begin", dwfl); dwfl_report_begin (dwfl); // We have a problem with -r REVISION vs -r BUILDDIR here. If // we're running against a fedora/rhel style kernel-debuginfo // tree, s.kernel_build_tree is not the place where the unstripped // vmlinux will be installed. Rather, it's over yonder at // /usr/lib/debug/lib/modules/$REVISION/. It seems that there is // no way to set the dwfl_callback.debuginfo_path and always // passs the plain kernel_release here. So instead we have to // hard-code this magic here. string lib_path = s.sysroot + "/lib/modules/" + s.kernel_release + "/build"; if (s.kernel_build_tree == lib_path) { if (s.sysroot != "") // If we have sysroot set does not make sense to pass // short release to dwfl, it won't take a sysroot into // account. Let's construct full path in such case. elfutils_kernel_path = string(s.sysroot + "/lib/modules/" + s.kernel_release); else elfutils_kernel_path = s.kernel_release; } else elfutils_kernel_path = s.kernel_build_tree; offline_modules_found = 0; // First try to report full path modules. set::iterator it = offline_search_names.begin(); int kernel = 0; while (it != offline_search_names.end()) { if ((*it)[0] == '/') { const char *cname = (*it).c_str(); Dwfl_Module *mod = dwfl_report_offline (dwfl, cname, cname, -1); if (mod) offline_modules_found++; } else if ((*it) == "kernel") kernel = 1; it++; } // We always need this, even when offline_search_modname is NULL // and offline_search_names is empty because we still might want // the kernel vmlinux reported. setup_dwfl_done = false; int rc = dwfl_linux_kernel_report_offline (dwfl, elfutils_kernel_path.c_str(), &setup_dwfl_report_kernel_p); (void) rc; /* Ignore since the predicate probably returned -1 at some point, And libdwfl interprets that as "whole query failed" rather than "found it already, stop looking". */ // NB: the result of an _offline call is the assignment of // virtualized addresses to relocatable objects such as // modules. These have to be converted to real addresses at // run time. See the dwarf_derived_probe ctor and its caller. // If no modules were found, and we are probing the kernel, // attempt to download the kernel debuginfo. if(kernel) { // Get the kernel build ID. We still need to call this even if we // already have the kernel debuginfo installed as it adds the // build ID to the script hash. string hex = get_kernel_build_id(s); if (offline_modules_found == 0 && s.download_dbinfo != 0 && !hex.empty()) { rc = download_kernel_debuginfo(s, hex); if(rc >= 0) { dwfl_end (dwfl); return setup_dwfl_kernel (modules_found, s); } } } DWFL_ASSERT ("dwfl_report_end", dwfl_report_end(dwfl, NULL, NULL)); *modules_found = offline_modules_found; return dwfl; } Dwfl* setup_dwfl_kernel(const std::string &name, unsigned *found, systemtap_session &s) { current_session_for_find_debuginfo = &s; const char *modname = name.c_str(); set names; // Default to empty /* Support full path kernel modules, these cannot be regular expressions, so just put them in the search set. */ if (name[0] == '/' || ! dwflpp::name_has_wildcard (modname)) { names.insert(name); modname = NULL; } offline_search_modname = modname; offline_search_names = names; return setup_dwfl_kernel(found, s); } Dwfl* setup_dwfl_kernel(const std::set &names, unsigned *found, systemtap_session &s) { current_session_for_find_debuginfo = &s; offline_search_modname = NULL; offline_search_names = names; return setup_dwfl_kernel(found, s); } Dwfl* setup_dwfl_user(const std::string &name) { Dwfl *dwfl = dwfl_begin (&user_callbacks); DWFL_ASSERT("dwfl_begin", dwfl); dwfl_report_begin (dwfl); // XXX: should support buildid-based naming const char *cname = name.c_str(); Dwfl_Module *mod = dwfl_report_offline (dwfl, cname, cname, -1); DWFL_ASSERT ("dwfl_report_end", dwfl_report_end(dwfl, NULL, NULL)); if (! mod) { dwfl_end(dwfl); dwfl = NULL; } return dwfl; } Dwfl* setup_dwfl_user(std::vector::const_iterator &begin, const std::vector::const_iterator &end, bool all_needed, systemtap_session &s) { current_session_for_find_debuginfo = &s; // See if we have this dwfl already cached set modset(begin, end); Dwfl *dwfl = dwfl_begin (&user_callbacks); DWFL_ASSERT("dwfl_begin", dwfl); dwfl_report_begin (dwfl); Dwfl_Module *mod = NULL; // XXX: should support buildid-based naming while (begin != end && dwfl != NULL) { const char *cname = (*begin).c_str(); mod = dwfl_report_offline (dwfl, cname, cname, -1); if (! mod && all_needed) { dwfl_end(dwfl); dwfl = NULL; } begin++; } /* Extract the build id and add it to the session variable * so it will be added to the script hash */ if (mod) { const unsigned char *bits; GElf_Addr vaddr; if(s.verbose > 2) clog << _("Extracting build ID.") << endl; int bits_length = dwfl_module_build_id(mod, &bits, &vaddr); /* Convert the binary bits to a hex string */ string hex = hex_dump(bits, bits_length); //Store the build ID in the session s.build_ids.push_back(hex); } if (dwfl) DWFL_ASSERT ("dwfl_report_end", dwfl_report_end(dwfl, NULL, NULL)); return dwfl; } bool is_user_module(const std::string &m) { return m[0] == '/' && m.rfind(".ko", m.length() - 1) != m.length() - 3; } int internal_find_debuginfo (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), const char *modname __attribute__ ((unused)), GElf_Addr base __attribute__ ((unused)), const char *file_name, const char *debuglink_file, GElf_Word debuglink_crc, char **debuginfo_file_name) { int bits_length; string hex; /* To Keep track of whether the abrt successfully installed the debuginfo */ static int install_dbinfo_failed = 0; /* Make sure the current session variable is not null */ if(current_session_for_find_debuginfo == NULL) goto call_dwfl_standard_find_debuginfo; /* Check to see if download-debuginfo=0 was set */ if(!current_session_for_find_debuginfo->download_dbinfo || abrt_path.empty()) goto call_dwfl_standard_find_debuginfo; /* Check that we haven't already run this */ if (install_dbinfo_failed < 0) { if(current_session_for_find_debuginfo->verbose > 1) current_session_for_find_debuginfo->print_warning(_F("We already tried running '%s'", abrt_path.c_str())); goto call_dwfl_standard_find_debuginfo; } /* Extract the build ID */ const unsigned char *bits; GElf_Addr vaddr; if(current_session_for_find_debuginfo->verbose > 2) clog << _("Extracting build ID.") << endl; bits_length = dwfl_module_build_id(mod, &bits, &vaddr); /* Convert the binary bits to a hex string */ hex = hex_dump(bits, bits_length); /* Search for the debuginfo with the build ID */ if(current_session_for_find_debuginfo->verbose > 2) clog << _F("Searching for debuginfo with build ID: '%s'.", hex.c_str()) << endl; if (bits_length > 0) { int fd = dwfl_build_id_find_debuginfo(mod, NULL, NULL, 0, NULL, NULL, 0, debuginfo_file_name); if (fd >= 0) return fd; } /* The above failed, so call abrt-action-install-debuginfo-to-abrt-cache to download and install the debuginfo */ if(current_session_for_find_debuginfo->verbose > 1) clog << _F("Downloading and installing debuginfo with build ID: '%s' using %s.", hex.c_str(), abrt_path.c_str()) << endl; struct tms tms_before; times (& tms_before); struct timeval tv_before; struct tms tms_after; unsigned _sc_clk_tck; struct timeval tv_after; gettimeofday (&tv_before, NULL); if(execute_abrt_action_install_debuginfo_to_abrt_cache (hex) < 0) { install_dbinfo_failed = -1; current_session_for_find_debuginfo->print_warning(_F("%s failed.", abrt_path.c_str())); goto call_dwfl_standard_find_debuginfo; } _sc_clk_tck = sysconf (_SC_CLK_TCK); times (& tms_after); gettimeofday (&tv_after, NULL); if(current_session_for_find_debuginfo->verbose > 1) clog << _("Download completed in ") << ((tms_after.tms_cutime + tms_after.tms_utime - tms_before.tms_cutime - tms_before.tms_utime) * 1000 / (_sc_clk_tck)) << "usr/" << ((tms_after.tms_cstime + tms_after.tms_stime - tms_before.tms_cstime - tms_before.tms_stime) * 1000 / (_sc_clk_tck)) << "sys/" << ((tv_after.tv_sec - tv_before.tv_sec) * 1000 + ((long)tv_after.tv_usec - (long)tv_before.tv_usec) / 1000) << "real ms"<< endl; call_dwfl_standard_find_debuginfo: /* Call the original dwfl_standard_find_debuginfo */ return dwfl_standard_find_debuginfo(mod, userdata, modname, base, file_name, debuglink_file, debuglink_crc, debuginfo_file_name); } int execute_abrt_action_install_debuginfo_to_abrt_cache (string hex) { /* Be sure that abrt exists */ if (abrt_path.empty()) return -1; int timeout = current_session_for_find_debuginfo->download_dbinfo;; vector cmd; cmd.push_back ("/bin/sh"); cmd.push_back ("-c"); /* NOTE: abrt does not currently work with asking for confirmation * in version abrt-2.0.3-1.fc15.x86_64, Bugzilla: BZ726192 */ if(current_session_for_find_debuginfo->download_dbinfo == -1) { cmd.push_back ("echo " + hex + " | " + abrt_path + " --ids=-"); timeout = INT_MAX; current_session_for_find_debuginfo->print_warning(_("Due to bug in abrt, it may continue downloading anyway without asking for confirmation.")); } else cmd.push_back ("echo " + hex + " | " + abrt_path + " -y --ids=-"); /* NOTE: abrt does not allow canceling the download process at the moment * in version abrt-2.0.3-1.fc15.x86_64, Bugzilla: BZ730107 */ if(timeout != INT_MAX) current_session_for_find_debuginfo->print_warning(_("Due to a bug in abrt, it may continue downloading after stopping stap if download times out.")); int pid; if(current_session_for_find_debuginfo->verbose > 1 || current_session_for_find_debuginfo->download_dbinfo == -1) /* Execute abrt-action-install-debuginfo-to-abrt-cache, * showing output from abrt */ pid = stap_spawn(current_session_for_find_debuginfo->verbose, cmd, NULL); else { /* Execute abrt-action-install-debuginfo-to-abrt-cache, * without showing output from abrt */ posix_spawn_file_actions_t fa; if (posix_spawn_file_actions_init(&fa) != 0) return -1; if(posix_spawn_file_actions_addopen(&fa, 1, "/dev/null", O_WRONLY, 0) != 0) { posix_spawn_file_actions_destroy(&fa); return -1; } pid = stap_spawn(current_session_for_find_debuginfo->verbose, cmd, &fa); posix_spawn_file_actions_destroy(&fa); } /* Check to see if either the program successfully completed, or if it timed out. */ int rstatus = 0; int timer = 0; int rc = 0; while(timer < timeout) { sleep(1); rc = waitpid(pid, &rstatus, WNOHANG); if(rc < 0) return -1; if (rc > 0 && WIFEXITED(rstatus)) break; assert_no_interrupts(); timer++; } if(timer == timeout) { /* Timed out! */ kill(-pid, SIGINT); current_session_for_find_debuginfo->print_warning(_("Aborted downloading debuginfo: timed out.")); return -1; } /* Successfully finished downloading! */ #if 0 // Should not print this until BZ733690 is fixed as abrt could fail to download // and it would still print success. if(current_session_for_find_debuginfo->verbose > 1 || current_session_for_find_debuginfo->download_dbinfo == -1) clog << _("Download Completed Successfully!") << endl; #endif if(current_session_for_find_debuginfo->verbose > 1 || current_session_for_find_debuginfo->download_dbinfo == -1) clog << _("ABRT finished attempting to download debuginfo.") << endl; return 0; } /* Get the kernel build ID */ string get_kernel_build_id(systemtap_session &s) { bool found = false; string hex; // Try to find BuildID from vmlinux.id string kernel_buildID_path = s.kernel_build_tree + "/vmlinux.id"; if(s.verbose > 2) clog << _F("Attempting to extract kernel debuginfo build ID from %s", kernel_buildID_path.c_str()) << endl; ifstream buildIDfile; buildIDfile.open(kernel_buildID_path.c_str()); if(buildIDfile.is_open()) { getline(buildIDfile, hex); if(buildIDfile.good()) { found = true; } buildIDfile.close(); } // Try to find BuildID from the notes file if the above didn't work and we are // building a native module if(found == false && s.native_build) { if(s.verbose > 1) clog << _("Attempting to extract kernel debuginfo build ID from /sys/kernel/notes") << endl; const char *notesfile = "/sys/kernel/notes"; int fd = open64 (notesfile, O_RDONLY); if (fd < 0) return ""; assert (sizeof (Elf32_Nhdr) == sizeof (GElf_Nhdr)); assert (sizeof (Elf64_Nhdr) == sizeof (GElf_Nhdr)); union { GElf_Nhdr nhdr; unsigned char data[8192]; } buf; ssize_t n = read (fd, buf.data, sizeof buf); close (fd); if (n <= 0) return ""; unsigned char *p = buf.data; while (p < &buf.data[n]) { /* No translation required since we are reading the native kernel. */ GElf_Nhdr *nhdr = (GElf_Nhdr *) p; p += sizeof *nhdr; unsigned char *name = p; p += (nhdr->n_namesz + 3) & -4U; unsigned char *bits = p; p += (nhdr->n_descsz + 3) & -4U; if (p <= &buf.data[n] && nhdr->n_type == NT_GNU_BUILD_ID && nhdr->n_namesz == sizeof "GNU" && !memcmp (name, "GNU", sizeof "GNU")) { // Found it. hex = hex_dump(bits, nhdr->n_descsz); found = true; } } } if(found) { return hex; } else return ""; } /* Find the kernel build ID and attempt to download the matching debuginfo */ int download_kernel_debuginfo (systemtap_session &s, string hex) { // NOTE: At some point we want to base the // already_tried_downloading_kernel_debuginfo flag on the build ID rather // than just the stap process. // Don't try this again if we already did. static int already_tried_downloading_kernel_debuginfo = 0; if(already_tried_downloading_kernel_debuginfo) return -1; // Attempt to download the debuginfo if(s.verbose > 1) clog << _F("Success! Extracted kernel debuginfo build ID: %s", hex.c_str()) << endl; int rc = execute_abrt_action_install_debuginfo_to_abrt_cache(hex); already_tried_downloading_kernel_debuginfo = 1; if (rc < 0) return -1; // Success! return 0; }