diff --git a/CMakeLists.txt b/CMakeLists.txt index 47b07e0acfc..376705dd551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -793,6 +793,9 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/src/preload/rr_page.ld" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scripts/rr-collect-symbols.py" "${CMAKE_CURRENT_BINARY_DIR}/bin/rr-collect-symbols.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scripts/rr-gdb-script-host.py" + "${CMAKE_CURRENT_BINARY_DIR}/bin/rr-gdb-script-host.py" + COPYONLY) install(PROGRAMS scripts/signal-rr-recording.sh scripts/rr-collect-symbols.py diff --git a/scripts/rr-gdb-script-host.py b/scripts/rr-gdb-script-host.py new file mode 100755 index 00000000000..22a9647452d --- /dev/null +++ b/scripts/rr-gdb-script-host.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" rr-gdb-script-host.py """ +# Performs a limited emulation of the runtime environment of gdb scripts so that +# we can run them for `rr sources` to locate debug information. +from typing import Optional, List, Callable +import logging +import os +import signal +import sys + +def strip_prefix(s: str, needle: str) -> Optional[str]: + if s.startswith(needle): + return s[len(needle):] + + return None + +GdbNewObjfileEventCallback = Callable[[object], None] + +class GdbScriptHost: + """ The filename of the main symbol file """ + _filename: str = "" + """ The current value of the gdb dir """ + _dir: str = "" + """ The current value of debug-file-directory """ + debug_file_directory: str = "/usr/lib/debug" + + new_objfile_events: List[GdbNewObjfileEventCallback] = [] + + def __init__(self, *args, **kwargs): + self._filename = args[0] + + def show(self, cmd: str) -> Optional[str]: + cmd.rstrip() + if cmd == "debug-file-directory": + return self.debug_file_directory + if cmd == "dir": + return "Source directories searched: %s:$cdir:$cwd"%self._dir + + return None + + def set(self, cmd: str) -> str: + dfd = strip_prefix(cmd, "debug-file-directory ") + if dfd: + self.debug_file_directory = dfd + # Prints nothing upon success. + return "" + + # This seems to be the default error message. + return "No symbol table is loaded. Use the \"file\" command." + + def execute_script(self, script: str): + gdb_api: GdbApiRoot = GdbApiRoot(self) + exec(script, {'gdb': gdb_api}) + + def new_objfile(self, f: str): + new_objfile: GdbNewObjfile = GdbNewObjfile(self, f) + new_objfile_event: GdbNewObjfileEvent = GdbNewObjfileEvent(self, new_objfile) + for callback in self.new_objfile_events: + callback(new_objfile_event) + +class GdbApiObject(object): + def __init__(self, *args, **kwargs): + self.gdb = args[0] + + def __getattr__(self, attr): + logging.warning("Accessing unsupported GDB api %s.%s" % (self.__class__.__name__, attr)) + +class GdbProgspace(GdbApiObject): + filename: str + + def __init__(self, *args, **kwargs): + GdbApiObject.__init__(self, *args, **kwargs) + self.filename = self.gdb._filename + +class GdbNewObjfile(GdbApiObject): + filename: str + def __init__(self, *args, **kwargs): + GdbApiObject.__init__(self, *args, **kwargs) + self.filename = args[1] + +class GdbNewObjfileEvent(GdbApiObject): + new_objfile: GdbNewObjfile + + def __init__(self, *args, **kwargs): + GdbApiObject.__init__(self, *args, **kwargs) + self.new_objfile = args[1] + +class GdbNewObjfileEvents(GdbApiObject): + def connect(self, c: GdbNewObjfileEventCallback): + logging.debug("EventRegistry.connect") + self.gdb.new_objfile_events.append(c) + +class GdbApiEvents(GdbApiObject): + _new_objfile: Optional[GdbNewObjfileEvents] = None + + @property + def new_objfile(self) -> GdbNewObjfileEvents: + logging.debug("gdb.events.new_objfile") + if self._new_objfile == None: + self._new_objfile = GdbNewObjfileEvents(self.gdb) + return self._new_objfile + +class GdbApiRoot(GdbApiObject): + _events: Optional[GdbApiEvents] = None + _current_progspace: Optional[GdbProgspace] = None + + def execute(self, command: str, from_tty: bool = False, to_string: bool = False) -> Optional[str]: + logging.debug("gdb.execute(\"%s\", from_tty=%s, to_string=%s)"%(command, str(from_tty), str(to_string))) + if from_tty: + logging.warning("Unsupported gdb.execute with from_tty == True") + return None + + remainder = strip_prefix(command, "show ") + if remainder: + r = self.gdb.show(remainder) + if to_string: + return r + else: + print(r, file=sys.stderr) + return None + remainder = strip_prefix(command, "set ") + if remainder: + r = self.gdb.set(remainder) + if to_string: + return r + else: + print(r, file=sys.stderr) + return None + remainder = strip_prefix(command, "dir ") + if remainder: + self.gdb._dir = remainder + s = "Source directories searched: %s:$cdir:$cwd"%remainder + if to_string: + return s + else: + print(s, file=sys.stderr) + return None + + logging.warning("Unsupported gdb.execute \"%s\""%command) + return None + + def lookup_global_symbol(self, s: str) -> Optional[object]: + #logging.debug("gdb.lookup_global_symbol(\"%s\")"%s) + logging.warning("gdb.lookup_global_symbol(\"%s\") is not yet implemented, pretending we found something"%s) + return object() + + def current_progspace(self) -> GdbProgspace: + logging.debug("gdb.current_progspace()") + if self._current_progspace == None: + self._current_progspace = GdbProgspace(self.gdb) + return self._current_progspace + + @property + def events(self) -> GdbApiEvents: + logging.debug("gdb.events") + if self._events == None: + self._events = GdbApiEvents(self.gdb) + return self._events + +if __name__ == '__main__': + with open(sys.argv[1], 'r') as user_script_file: + user_script = user_script_file.read() + host = GdbScriptHost(sys.argv[2]) + try: + host.execute_script(user_script) + except NameError as e: + if getattr(e, "name", None) == "python" or e == "NameError: name 'python' is not defined": + # This might be a gdb script that wraps a python script. + start = user_script.find("python") + len("python") + end = user_script.find("end") + if end == -1: + raise(e) + user_script = user_script[start:end] + try: + host.execute_script(user_script) + except: + raise(e) + + print("%s\n%s" % (host.debug_file_directory, host._dir), flush=True) + for line in sys.stdin: + line = line.rstrip() + logging.debug("Processing %s"%line) + host.new_objfile(line) + print("%s\n%s" % (host.debug_file_directory, host._dir), flush=True) diff --git a/src/ElfReader.cc b/src/ElfReader.cc index a25671a9b94..4730e09e770 100644 --- a/src/ElfReader.cc +++ b/src/ElfReader.cc @@ -541,7 +541,7 @@ DwarfSpan ElfReader::dwarf_section(const char* name, bool known_to_be_compressed offsets.compressed |= known_to_be_compressed; if (offsets.start && offsets.compressed) { auto decompressed = impl().decompress_section(offsets); - return DwarfSpan(&decompressed->front(), &decompressed->back()); + return DwarfSpan(decompressed->data(), decompressed->data() + decompressed->size()); } return DwarfSpan(map + offsets.start, map + offsets.end); } diff --git a/src/SourcesCommand.cc b/src/SourcesCommand.cc index 137637c976a..bf29868c138 100644 --- a/src/SourcesCommand.cc +++ b/src/SourcesCommand.cc @@ -1,8 +1,10 @@ /* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ #include +#include #include +#include #include #include #include @@ -12,7 +14,9 @@ #include "Command.h" #include "ElfReader.h" -#include "RecordSession.h" +#include "Flags.h" +#include "ReplaySession.h" +#include "StringVectorToCharArray.h" #include "TraceStream.h" #include "core.h" #include "cpp_supplement.h" @@ -66,7 +70,10 @@ SourcesCommand SourcesCommand::singleton( " in the library's DW_AT_comp_dir property\n" " for all compilation units.\n" " LIBRARY is the basename of the original file name,\n" - " e.g. libc-2.32.so\n"); + " e.g. libc-2.32.so\n" + " --gdb-script=SCRIPT Runs the provided gdb script in a (very basic)\n" + " emulator, and gives it a chance to change paths\n" + " for symbol loading/etc\n"); class ExplicitSourcesCommand : public Command { public: @@ -90,6 +97,11 @@ ExplicitSourcesCommand ExplicitSourcesCommand::singleton( " LIBRARY is the basename of the original file name,\n" " e.g. libc-2.32.so\n"); +struct SourcesFlags { + map comp_dir_substitutions; + string gdb_script; +}; + static void dir_name(string& s) { size_t p = s.rfind('/'); if (p == string::npos || (p == 0 && s.size() == 1)) { @@ -130,6 +142,186 @@ struct DirExistsCache { } }; +struct DebugDirs { + vector debug_file_directories; + vector source_directories; +}; + +/// Manages integration with rr-gdb-script-host.py to allow a gdb script to +/// control which directories we search. If input_pipe_fd is open we have a +/// python child process. If input_pipe_fd is closed then everything here +/// becomes a no-op. +class DebugDirManager { +public: + DebugDirManager(const string& program, const string& gdb_script); + ~DebugDirManager(); + + DebugDirs initial_directories() { + return read_result(); + } + DebugDirs process_one_binary(const string& binary_path); + +private: + DebugDirManager(const DebugDirManager&) = delete; + DebugDirManager& operator=(const DebugDirManager&) = delete; + + DebugDirs read_result(); + + ScopedFd input_pipe_fd; + FILE* output_file; + pid_t pid; +}; + +DebugDirManager::~DebugDirManager() { + if (!input_pipe_fd.is_open()) { + return; + } + + input_pipe_fd.close(); + fclose(output_file); + + int status; + if (waitpid(pid, &status, 0) == -1) { + FATAL() << "Failed to wait on gdb script host"; + } +} + +DebugDirManager::DebugDirManager(const string& program, const string& gdb_script) + : pid(-1) +{ + if (gdb_script.empty()) { + return; + } + + int stdin_pipe_fds[2]; + if (pipe(stdin_pipe_fds) == -1) { + FATAL(); + } + int stdout_pipe_fds[2]; + if (pipe(stdout_pipe_fds) == -1) { + FATAL(); + } + + posix_spawn_file_actions_t file_actions; + int ret = posix_spawn_file_actions_init(&file_actions); + if (ret != 0) { + FATAL() << "posix_spawn_file_actions_init failed with " << ret; + } + + // Close unused write end in the child. + ret = posix_spawn_file_actions_addclose(&file_actions, stdin_pipe_fds[1]); + if (ret != 0) { + FATAL() << "posix_spawn_file_actions_addclose failed with " << ret; + } + + // Close unused read end in the child. + ret = posix_spawn_file_actions_addclose(&file_actions, stdout_pipe_fds[0]); + if (ret != 0) { + FATAL() << "posix_spawn_file_actions_addclose failed with " << ret; + } + + // Replace child's stdin with the read end. + ret = posix_spawn_file_actions_adddup2(&file_actions, stdin_pipe_fds[0], 0); + if (ret != 0) { + FATAL() << "posix_spawn_file_actions_adddup2 failed with " << ret; + } + + // Replace child's stdout with the write end. + ret = posix_spawn_file_actions_adddup2(&file_actions, stdout_pipe_fds[1], 1); + if (ret != 0) { + FATAL() << "posix_spawn_file_actions_adddup2 failed with " << ret; + } + + string gdb_script_host_path = resource_path() + "bin/rr-gdb-script-host.py"; + pid_t pid; + vector gdb_script_host_argv_vec = { gdb_script_host_path, gdb_script, program }; + StringVectorToCharArray gdb_script_host_argv(gdb_script_host_argv_vec); + ret = posix_spawn(&pid, gdb_script_host_path.c_str(), &file_actions, nullptr, + gdb_script_host_argv.get(), environ); + if (ret != 0) { + FATAL() << "posix_spawn failed with " << ret; + } + + // Ignore the return values during cleanup. + posix_spawn_file_actions_destroy(&file_actions); + + close(stdin_pipe_fds[0]); + close(stdout_pipe_fds[1]); + + this->pid = pid; + this->input_pipe_fd = ScopedFd(stdin_pipe_fds[1]); + this->output_file = fdopen(stdout_pipe_fds[0], "r"); + if (!this->output_file) { + FATAL() << "Failed to fdopen(stdout_pipe_fds[0])"; + } +} + +DebugDirs DebugDirManager::process_one_binary(const string& binary_path) { + if (!input_pipe_fd.is_open()) { + return DebugDirs(); + } + + auto len = binary_path.length(); + size_t written = write(input_pipe_fd, binary_path.c_str(), len); + if (written != len) { + FATAL() << "Failed to write filename"; + } + written = write(input_pipe_fd, "\n", 1); + if (written != 1) { + FATAL() << "Failed to write trailing newline"; + } + + return read_result(); +} + +DebugDirs DebugDirManager::read_result() { + char buf[4096]; + DebugDirs result; + size_t index; + const char delimiter[2] = ":"; + + if (!input_pipe_fd.is_open()) { + return result; + } + + if (!fgets(buf, sizeof(buf) - 1, output_file)) { + FATAL() << "Failed to read gdb script output"; + } + index = strcspn(buf, "\n"); + buf[index] = 0; + + char* token = strtok(buf, delimiter); + while (token != nullptr) { + string s(token); + s = real_path(s); + result.debug_file_directories.push_back(s); + LOG(debug) << "gdb script added debug dir '" << s << "'"; + token = strtok(nullptr, delimiter); + } + + if (!fgets(buf, sizeof(buf) - 1, output_file)) { + FATAL() << "Failed to read gdb script output"; + } + index = strcspn(buf, "\n"); + buf[index] = 0; + + token = strtok(buf, delimiter); + while (token != nullptr) { + char* buf = realpath(token, nullptr); + if (buf) { + auto s = string(buf); + result.source_directories.push_back(s); + LOG(debug) << "gdb script added source dir '" << s << "'"; + free(buf); + } else { + LOG(debug) << "realpath(" << token << ") = " << strerror(errno); + } + token = strtok(nullptr, delimiter); + } + + return result; +} + // Resolve a file name relative to a compilation directory and relative directory. // file_name cannot be null, but the others can be. // Takes into account the original file name as follows: @@ -208,11 +400,19 @@ struct DwoInfo { uint64_t id; }; +struct OutputCompDirSubstitution { + string trace_relative_name; + string substitution; +}; + static bool process_compilation_units(ElfFileReader& reader, ElfFileReader* sup_reader, const string& trace_relative_name, const string& original_file_name, const string& comp_dir_substitution, + vector& comp_dir_substitutions, + const string* debug_file_directory, + const string* debug_src_directory, set* file_names, vector* dwos, DirExistsCache& dir_exists_cache) { string build_id = reader.read_buildid(); @@ -264,15 +464,27 @@ static bool process_compilation_units(ElfFileReader& reader, } else { cu.set_str_offsets_base(0); } - const char* original_comp_dir = cu.die().string_attr(cu, DW_AT_comp_dir, debug_strs, &ok);; - const char* comp_dir; + const char* original_comp_dir = cu.die().string_attr(cu, DW_AT_comp_dir, debug_strs, &ok); + string comp_dir; if (!comp_dir_substitution.empty()) { - comp_dir = comp_dir_substitution.c_str(); + comp_dir = comp_dir_substitution; } else { - comp_dir = original_comp_dir; if (!ok) { continue; } + if (original_comp_dir) { + comp_dir = original_comp_dir; + } + if (debug_src_directory && !is_absolute(comp_dir)) { + prepend_path(debug_src_directory->c_str(), comp_dir); + if (std::find_if(comp_dir_substitutions.begin(), comp_dir_substitutions.end(), [trace_relative_name](OutputCompDirSubstitution& s) { + return s.trace_relative_name == trace_relative_name; + }) == comp_dir_substitutions.end()) { + comp_dir_substitutions.push_back({ trace_relative_name, comp_dir }); + } + } else if (debug_file_directory) { + prepend_path(debug_file_directory->c_str(), comp_dir); + } } const char* dwo_name = cu.die().string_attr(cu, DW_AT_GNU_dwo_name, debug_strs, &ok); if (!ok || !dwo_name) { @@ -295,11 +507,8 @@ static bool process_compilation_units(ElfFileReader& reader, } if (has_dwo_id) { string full_name; - if (resolve_file_name(original_file_name.c_str(), comp_dir, original_comp_dir, comp_dir_substitution, nullptr, dwo_name, dir_exists_cache, full_name)) { - string c; - if (comp_dir) { - c = comp_dir; - } + if (resolve_file_name(original_file_name.c_str(), comp_dir.c_str(), original_comp_dir, comp_dir_substitution, nullptr, dwo_name, dir_exists_cache, full_name)) { + string c = comp_dir; dwos->push_back({ dwo_name, trace_relative_name, build_id, std::move(c), full_name, dwo_id }); } else { FATAL() << "DWO missing due to relative path " << full_name; @@ -314,7 +523,7 @@ static bool process_compilation_units(ElfFileReader& reader, } if (source_file_name) { string full_name; - if (resolve_file_name(original_file_name.c_str(), comp_dir, original_comp_dir, comp_dir_substitution, nullptr, source_file_name, dir_exists_cache, full_name)) { + if (resolve_file_name(original_file_name.c_str(), comp_dir.c_str(), original_comp_dir, comp_dir_substitution, nullptr, source_file_name, dir_exists_cache, full_name)) { file_names->insert(full_name); } } @@ -333,7 +542,7 @@ static bool process_compilation_units(ElfFileReader& reader, } const char* dir = lines.directories()[f.directory_index]; string full_name; - if (resolve_file_name(original_file_name.c_str(), comp_dir, original_comp_dir, comp_dir_substitution, dir, f.file_name, dir_exists_cache, full_name)) { + if (resolve_file_name(original_file_name.c_str(), comp_dir.c_str(), original_comp_dir, comp_dir_substitution, dir, f.file_name, dir_exists_cache, full_name)) { file_names->insert(full_name); } } @@ -366,7 +575,8 @@ struct ExternalDebugInfo { static unique_ptr find_auxiliary_file(const string& original_file_name, const string& aux_file_name, - string& full_file_name) { + string& full_file_name, + const vector& dirs) { if (aux_file_name.empty()) { return nullptr; } @@ -425,6 +635,17 @@ find_auxiliary_file(const string& original_file_name, } LOG(info) << "Can't find external debuginfo file " << full_file_name; + // Try in an appropriate subdirectory of the provided debug dirs + for (auto& d : dirs) { + full_file_name = d + original_file_dir + "/" + aux_file_name; + normalize_file_name(full_file_name); + fd = ScopedFd(full_file_name.c_str(), O_RDONLY); + if (fd.is_open()) { + goto found; + } + LOG(info) << "Can't find external debuginfo file " << full_file_name; + } + // On Ubuntu 20.04 there's both a /lib/x86_64-linux-gnu/libc-2.31.so and a // /usr/lib/x86_64-linux-gnu/libc-2.31.so. They are hardlinked to the same inode, // and glibc debuginfo is present in the location corresponding to @@ -456,7 +677,9 @@ find_auxiliary_file(const string& original_file_name, } static unique_ptr -find_auxiliary_file_by_buildid(ElfFileReader& trace_file_reader, string& full_file_name) { +find_auxiliary_file_by_buildid(ElfFileReader& trace_file_reader, + string& full_file_name, + const vector& dirs) { string build_id = trace_file_reader.read_buildid(); if (build_id.empty()) { LOG(warn) << "Main ELF binary has no build ID!"; @@ -467,10 +690,22 @@ find_auxiliary_file_by_buildid(ElfFileReader& trace_file_reader, string& full_fi return nullptr; } - string path = "/usr/lib/debug/.build-id/" + build_id.substr(0, 2) + "/" + build_id.substr(2) + ".debug"; + string filename = build_id.substr(0, 2) + "/" + build_id.substr(2) + ".debug"; + string path = "/usr/lib/debug/.build-id/" + filename; ScopedFd fd(path.c_str(), O_RDONLY); if (!fd.is_open()) { LOG(info) << "Can't find external debuginfo file " << path; + for (auto &d : dirs) { + path = d + "/.build-id/" + filename; + fd = ScopedFd(path.c_str(), O_RDONLY); + if (fd.is_open()) { + break; + } + LOG(info) << "Can't find external debuginfo file " << path; + } + } + + if (!fd.is_open()) { return nullptr; } @@ -493,7 +728,10 @@ static bool process_auxiliary_file(ElfFileReader& trace_file_reader, set* file_names, const string& full_aux_file_name, const char* file_type, - const map& comp_dir_substitutions, + map comp_dir_substitutions, + vector& output_comp_dir_substitutions, + const string* chosen_debug_dir, + const string* chosen_src_dir, vector* dwos, set* external_debug_info, bool already_used_file, @@ -512,12 +750,16 @@ static bool process_auxiliary_file(ElfFileReader& trace_file_reader, LOG(debug) << "\tFound comp_dir substitution " << it->second; did_work = process_compilation_units(aux_file_reader, alt_file_reader, trace_relative_name, original_file_name, - it->second, file_names, dwos, dir_exists_cache); + it->second, output_comp_dir_substitutions, + chosen_debug_dir, chosen_src_dir, + file_names, dwos, dir_exists_cache); } else { LOG(debug) << "\tNo comp_dir substitution found"; did_work = process_compilation_units(aux_file_reader, alt_file_reader, trace_relative_name, original_file_name, - {}, file_names, dwos, dir_exists_cache); + {}, output_comp_dir_substitutions, + chosen_debug_dir, chosen_src_dir, + file_names, dwos, dir_exists_cache); } if (!did_work) { @@ -537,37 +779,63 @@ static bool try_debuglink_file(ElfFileReader& trace_file_reader, const string& trace_relative_name, const string& original_file_name, set* file_names, const string& aux_file_name, - const map& comp_dir_substitutions, + map& comp_dir_substitutions, + vector& output_comp_dir_substitutions, + unique_ptr& debug_dirs, + DebugDirs& dd, vector* dwos, set* external_debug_info, DirExistsCache& dir_exists_cache) { string full_file_name; auto reader = find_auxiliary_file(original_file_name, aux_file_name, - full_file_name); + full_file_name, dd.debug_file_directories); if (!reader) { - reader = find_auxiliary_file_by_buildid(trace_file_reader, full_file_name); + reader = find_auxiliary_file_by_buildid(trace_file_reader, full_file_name, dd.debug_file_directories); if (!reader) { + dd.debug_file_directories.clear(); + dd.source_directories.clear(); return false; } } + if (debug_dirs) { + dd = debug_dirs->process_one_binary(full_file_name); + } + /* A debuglink file can have its own debugaltlink */ string full_altfile_name; Debugaltlink debugaltlink = reader->read_debugaltlink(); auto altlink_reader = find_auxiliary_file(original_file_name, debugaltlink.file_name, - full_altfile_name); + full_altfile_name, dd.debug_file_directories); + + // Notify a gdb script about the main binary before processing CUs. + if (debug_dirs) { + dd = debug_dirs->process_one_binary(original_file_name); + } + + const string* chosen_debug_dir = nullptr; + if (!dd.debug_file_directories.empty()) { + // XXXkhuey what do we do if there's more than one directory? + chosen_debug_dir = &dd.debug_file_directories.back(); + } + const string* chosen_src_dir = nullptr; + if (!dd.source_directories.empty()) { + chosen_src_dir = &dd.source_directories.back(); + } bool has_source_files = process_auxiliary_file(trace_file_reader, *reader, altlink_reader.get(), trace_relative_name, original_file_name, file_names, full_file_name, DEBUGLINK, - comp_dir_substitutions, + comp_dir_substitutions, output_comp_dir_substitutions, + chosen_debug_dir, chosen_src_dir, dwos, external_debug_info, false, dir_exists_cache); if (altlink_reader) { has_source_files |= process_auxiliary_file(trace_file_reader, *altlink_reader, nullptr, trace_relative_name, original_file_name, file_names, full_altfile_name, DEBUGALTLINK, - comp_dir_substitutions, + comp_dir_substitutions, output_comp_dir_substitutions, + chosen_debug_dir, chosen_src_dir, dwos, external_debug_info, has_source_files, dir_exists_cache); } return has_source_files; @@ -751,12 +1019,11 @@ static bool starts_with(const string& s, const string& prefix) { return strncmp(s.c_str(), prefix.c_str(), prefix.size()) == 0; } -struct OutputCompDirSubstitution { - string trace_relative_name; - string substitution; -}; - -static int sources(const map& binary_file_names, const map& comp_dir_substitutions, bool is_explicit) { +template +static int sources(const iterable& binary_file_names, + map& comp_dir_substitutions, + unique_ptr& debug_dirs, + bool is_explicit) { vector relevant_binary_names; // Must be absolute. set file_names; @@ -764,6 +1031,11 @@ static int sources(const map& binary_file_names, const map dwos; vector output_comp_dir_substitutions; DirExistsCache dir_exists_cache; + DebugDirs dd; + if (debug_dirs) { + dd = debug_dirs->initial_directories(); + } + for (auto& pair : binary_file_names) { string trace_relative_name = pair.first; string original_name = pair.second; @@ -786,7 +1058,7 @@ static int sources(const map& binary_file_names, const map& binary_file_names, const mapsecond }); has_source_files = process_compilation_units(reader, altlink_reader.get(), trace_relative_name, pair.second, - it->second, &file_names, &dwos, dir_exists_cache); + it->second, output_comp_dir_substitutions, + nullptr, nullptr, &file_names, &dwos, + dir_exists_cache); } else { LOG(debug) << "\tNo comp_dir substitution found"; has_source_files = process_compilation_units(reader, altlink_reader.get(), trace_relative_name, pair.second, - {}, &file_names, &dwos, dir_exists_cache); + {}, output_comp_dir_substitutions, + nullptr, nullptr, &file_names, &dwos, + dir_exists_cache); } /* If the original binary had source files, force the inclusion of any debugaltlink * file, even if it does not itself have compilation units (it may have relevant strings) @@ -812,15 +1088,19 @@ static int sources(const map& binary_file_names, const mapprocess_one_binary(pair.first); + } + if (altlink_reader) { has_source_files |= process_auxiliary_file(reader, *altlink_reader, nullptr, trace_relative_name, pair.second, &file_names, full_altfile_name, - DEBUGALTLINK, comp_dir_substitutions, - &dwos, &external_debug_info, + DEBUGALTLINK, comp_dir_substitutions, output_comp_dir_substitutions, + nullptr, nullptr, &dwos, &external_debug_info, original_had_source_files, dir_exists_cache); } @@ -962,13 +1242,14 @@ static int sources(const map& binary_file_names, const map& args, map& comp_dir_substitutions) { +static bool parse_sources_option(vector& args, SourcesFlags& flags) { if (parse_global_option(args)) { return true; } static const OptionSpec options[] = { - { 0, "substitute", HAS_PARAMETER } + { 0, "substitute", HAS_PARAMETER }, + { 1, "gdb-script", HAS_PARAMETER } }; ParsedOption opt; @@ -982,18 +1263,26 @@ static bool parse_sources_option(vector& args, map& comp if (pos != string::npos) { auto k = opt.value.substr(0, pos); auto v = opt.value.substr(pos+1); - comp_dir_substitutions.insert(std::pair(k, v)); + flags.comp_dir_substitutions.insert(std::pair(k, v)); } break; } + case 1: { + flags.gdb_script = opt.value; + break; + } } return true; } int SourcesCommand::run(vector& args) { - map comp_dir_substitutions; - while (parse_sources_option(args, comp_dir_substitutions)) { + // Various "cannot replay safely..." warnings cannot affect us since + // we only replay to the first execve. + Flags::get_for_init().suppress_environment_warnings = true; + + SourcesFlags flags; + while (parse_sources_option(args, flags)) { } // (Trace file name, original file name) pairs @@ -1024,12 +1313,46 @@ int SourcesCommand::run(vector& args) { } } - return sources(binary_file_names, comp_dir_substitutions, false); + string program; + { + ReplaySession::Flags flags; + flags.redirect_stdio = false; + flags.share_private_mappings = false; + flags.replay_stops_at_first_execve = true; + flags.cpu_unbound = true; + + ReplaySession::shr_ptr replay_session = ReplaySession::create(trace_dir, flags); + while (true) { + auto result = replay_session->replay_step(RUN_CONTINUE); + if (replay_session->done_initial_exec()) { + program = replay_session->vms()[0]->exe_image(); + break; + } + + if (result.status == REPLAY_EXITED) { + break; + } + } + } + + std::vector> binary_file_names_ordered; + auto i = binary_file_names.find(program); + if (i != binary_file_names.end()) { + binary_file_names_ordered.push_back(*i); + binary_file_names.erase(program); + } + std::copy(binary_file_names.begin(), binary_file_names.end(), std::back_inserter(binary_file_names_ordered)); + auto debug_dirs = make_unique(program, flags.gdb_script); + return sources(binary_file_names_ordered, flags.comp_dir_substitutions, debug_dirs, false); } int ExplicitSourcesCommand::run(vector& args) { - map comp_dir_substitutions; - while (parse_sources_option(args, comp_dir_substitutions)) { + // Various "cannot replay safely..." warnings cannot affect us since + // we only replay to the first execve. + Flags::get_for_init().suppress_environment_warnings = true; + + SourcesFlags flags; + while (parse_sources_option(args, flags)) { } // (Trace file name, original file name) pairs @@ -1059,7 +1382,8 @@ int ExplicitSourcesCommand::run(vector& args) { binary_file_names.insert(make_pair(std::move(buildid), arg)); } - return sources(binary_file_names, comp_dir_substitutions, true); + unique_ptr debug_dirs; + return sources(binary_file_names, flags.comp_dir_substitutions, debug_dirs, true); } } // namespace rr