| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // clang-format off |
| // Included early because of conflicts. |
| #include "src/lib/elflib/elflib.h" |
| // clang-format on |
| |
| #include "src/developer/debug/debug_agent/linux_utils.h" |
| |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| |
| #include <optional> |
| #include <sstream> |
| |
| #include <fbl/unique_fd.h> |
| #include <linux/auxvec.h> |
| #include <linux/limits.h> |
| |
| #include "src/developer/debug/debug_agent/elf_utils.h" |
| #include "src/developer/debug/debug_agent/process_handle.h" |
| #include "src/lib/files/eintr_wrapper.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/strings/split_string.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace debug_agent { |
| namespace linux { |
| |
| namespace { |
| |
| // Syscall ID of the pidfd_open() function. From "man pidfd_open". |
| constexpr long kPidFDOpenSyscallID = 434; |
| |
| // Reads "/proc/<pid>/<name>" and returns it as a single string. Returns the empty string on error. |
| std::string ReadProcFile(int pid, const char* name) { |
| std::string file_name = fxl::StringPrintf("/proc/%d/%s", pid, name); |
| |
| std::string result; |
| files::ReadFileToString(file_name, &result); |
| return result; |
| } |
| |
| // Returns if this path is composed only of numeric digits. |
| bool IsNumericPath(const std::filesystem::path& path) { |
| for (char c : path.string()) { |
| if (!isdigit(c)) |
| return false; |
| } |
| return true; |
| } |
| |
| // Converts a /proc/* path to a PID if it references one. Otherwise returns nullopt. |
| std::optional<int> PathToPid(const std::filesystem::path& path) { |
| auto last_part = path.filename(); |
| if (!IsNumericPath(last_part)) |
| return std::nullopt; |
| |
| return atoi(last_part.c_str()); |
| } |
| |
| } // namespace |
| |
| fbl::unique_fd OpenPidfd(int pid) { return fbl::unique_fd(syscall(kPidFDOpenSyscallID, pid, 0)); } |
| |
| std::optional<user_regs_struct> ReadGeneralRegisters(int pid) { |
| user_regs_struct regs; |
| |
| iovec vec; |
| vec.iov_base = ®s; |
| vec.iov_len = sizeof(regs); |
| if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &vec) == -1) { |
| return std::nullopt; |
| } |
| return regs; |
| } |
| |
| zx_status_t WriteGeneralRegisters(int pid, const user_regs_struct& regs) { |
| iovec vec; |
| vec.iov_base = const_cast<void*>(static_cast<const void*>(®s)); |
| vec.iov_len = sizeof(regs); |
| if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &vec) == -1) |
| return ZX_ERR_INTERNAL; |
| return ZX_OK; |
| } |
| |
| std::vector<MapEntry> ParseMaps(const std::string& contents) { |
| std::vector<MapEntry> result; |
| |
| std::istringstream in(contents); |
| while (in.good()) { |
| MapEntry entry; |
| |
| // <address>-<address> |
| uint64_t begin = 0, end = 0; |
| char hyphen = 0; |
| in >> std::hex >> begin >> hyphen >> std::hex >> end; |
| if (!in.good() || end < begin) |
| return result; |
| entry.range = debug::AddressRange(begin, end); |
| |
| // Flags. |
| char r_bit, w_bit, x_bit, s_bit; |
| in >> r_bit >> w_bit >> x_bit >> s_bit; |
| if (!in.good()) |
| return result; |
| entry.read = r_bit == 'r'; |
| entry.write = w_bit == 'w'; |
| entry.execute = x_bit == 'x'; |
| entry.shared = s_bit == 's'; |
| |
| // Offset, device, inode. |
| in >> std::hex >> entry.offset >> entry.device >> std::dec >> entry.inode; |
| |
| // Everything else to the end is the path (could be empty). |
| while (in.good() && in.peek() == ' ') |
| in.get(); // Skip whitespace before. |
| std::getline(in, entry.path); |
| |
| result.push_back(std::move(entry)); |
| } |
| return result; |
| } |
| |
| std::vector<MapEntry> GetMaps(int pid) { return ParseMaps(ReadProcFile(pid, "maps")); } |
| |
| std::map<std::string, std::string> ParseStatus(const std::string& contents) { |
| std::istringstream in(contents); |
| |
| std::map<std::string, std::string> result; |
| do { |
| std::string line; |
| std::getline(in, line); |
| |
| // Split on the first colon. |
| std::string key, value; |
| for (size_t i = 0; i < line.size(); i++) { |
| if (line[i] == ':') { |
| key = line.substr(0, i); |
| |
| // Skip spaces. |
| i++; |
| while (i < line.size() && (line[i] == ' ' || line[i] == '\t')) |
| i++; |
| |
| // Everything after that is the value. |
| value = line.substr(i); |
| break; |
| } |
| } |
| if (key.empty() && value.empty()) |
| break; |
| |
| result[key] = std::move(value); |
| } while (in.good()); |
| |
| return result; |
| } |
| |
| std::map<std::string, std::string> GetStatus(int pid) { |
| return ParseStatus(ReadProcFile(pid, "status")); |
| } |
| |
| std::string GetExe(int pid) { |
| std::string link_path = fxl::StringPrintf("/proc/%d/exe", pid); |
| |
| char dest_path[PATH_MAX]; // Not null terminated! |
| ssize_t result = readlink(link_path.c_str(), dest_path, PATH_MAX); |
| if (result > 0) |
| return std::string(dest_path, result); |
| |
| // Some links aren't set, so fall back on the name from the proc status. |
| return GetStatus(pid)["Name"]; |
| } |
| |
| std::vector<std::string> GetCommandLine(int pid) { |
| // /proc/<pid>/cmdline is a null-separated view of the command line. |
| return fxl::SplitStringCopy(ReadProcFile(pid, "cmdline"), std::string_view("\0", 1), |
| fxl::kKeepWhitespace, fxl::kSplitWantNonEmpty); |
| } |
| |
| uint64_t GetLdSoLoadAddress(int pid) { return ReadAuxv(pid)[AT_BASE]; } |
| |
| std::optional<LdSoInfo> GetLdSoInfo(int pid) { |
| uint64_t load_addr = GetLdSoLoadAddress(pid); |
| if (!load_addr) |
| return std::nullopt; |
| |
| for (const MapEntry& entry : GetMaps(pid)) { |
| if (entry.range.begin() == load_addr) |
| return LdSoInfo{.load_address = load_addr, .path = entry.path}; |
| } |
| return std::nullopt; |
| } |
| |
| uint64_t GetLdSoDebugAddress(int pid) { |
| auto info = GetLdSoInfo(pid); |
| if (!info) |
| return 0; |
| |
| if (uint64_t relative_addr = GetLdSoDebugRelativeAddress(info->path)) |
| return info->load_address + relative_addr; |
| return 0; |
| } |
| |
| uint64_t GetLdSoBreakpointAddress(int pid) { |
| auto info = GetLdSoInfo(pid); |
| if (!info) |
| return 0; |
| |
| if (uint64_t relative_addr = GetLdSoBreakpointRelativeAddress(info->path)) |
| return info->load_address + relative_addr; |
| return 0; |
| } |
| |
| std::map<uint64_t, uint64_t> ReadAuxv(int pid) { |
| std::map<uint64_t, uint64_t> result; |
| |
| std::string contents = ReadProcFile(pid, "auxv"); |
| size_t key_count = contents.size() / sizeof(uint64_t); |
| const uint64_t* raw = reinterpret_cast<const uint64_t*>(contents.data()); |
| |
| for (size_t i = 0; i < key_count; i++) |
| result[raw[i * 2]] = raw[i * 2 + 1]; |
| |
| return result; |
| } |
| |
| std::optional<ThreadHandle::State> ThreadStateFromLinuxState(const std::string& linux_state) { |
| using State = debug_ipc::ThreadRecord::State; |
| using BlockedReason = debug_ipc::ThreadRecord::BlockedReason; |
| |
| if (linux_state.empty()) |
| return std::nullopt; |
| |
| // The first character gives the state. |
| // TODO(brettw) We should probably extend our thread state options to better cover the Linux ones. |
| switch (linux_state[0]) { |
| case 'S': // Sleeping |
| return ThreadHandle::State(BlockedReason::kSleeping); |
| case 'D': // Waiting in uninterruptible disk sleep |
| return ThreadHandle::State(BlockedReason::kSleeping); |
| case 'T': // Stopped (on a signal) or (before Linux 2.6.33) trace stopped |
| return ThreadHandle::State(BlockedReason::kException); |
| case 't': // Tracing stop (Linux 2.6.33 onward) |
| return ThreadHandle::State(State::kSuspended); |
| case 'K': // Wakekill (Linux 2.6.33 to 3.13 only) |
| case 'W': // Waking (Linux 2.6.33 to 3.13 only) |
| return ThreadHandle::State(State::kRunning); |
| case 'P': // Parked (Linux 3.9 to 3.13 only) |
| return ThreadHandle::State(BlockedReason::kSleeping); |
| case 'X': // Dead (from Linux 2.6.0 onward) |
| case 'x': // Dead (Linux 2.6.33 to 3.13 only) |
| case 'Z': // Zombie |
| return ThreadHandle::State(State::kDead); |
| case 'R': // Running |
| return ThreadHandle::State(State::kRunning); |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| std::vector<int> GetDirectoryPids(const std::filesystem::path& path) { |
| std::vector<int> result; |
| |
| std::error_code ec; |
| for (const auto& child : std::filesystem::directory_iterator(path, ec)) { |
| if (std::filesystem::is_directory(child.path(), ec)) { |
| if (auto parsed = PathToPid(child.path())) |
| result.push_back(*parsed); |
| } |
| } |
| return result; |
| } |
| |
| std::string SignalToString(int signal, bool include_number) { |
| // clang-format off |
| const char* name = nullptr; |
| switch (signal) { |
| case SIGABRT: name = "SIGABRT"; break; |
| case SIGALRM: name = "SIGALRM"; break; |
| case SIGBUS: name = "SIGBUS"; break; |
| case SIGCHLD: name = "SIGCHLD"; break; |
| case SIGCONT: name = "SIGCONT"; break; |
| case SIGFPE: name = "SIGFPE"; break; |
| case SIGHUP: name = "SIGHUP"; break; |
| case SIGILL: name = "SIGILL"; break; |
| case SIGINT: name = "SIGINT"; break; |
| case SIGKILL: name = "SIGKILL"; break; |
| case SIGPIPE: name = "SIGPIPE"; break; |
| case SIGPOLL: name = "SIGPOLL"; break; |
| case SIGPROF: name = "SIGPROF"; break; |
| case SIGPWR: name = "SIGPWR"; break; |
| case SIGQUIT: name = "SIGQUIT"; break; |
| case SIGSEGV: name = "SIGSEGV"; break; |
| case SIGSTKFLT: name = "SIGSTKFLT"; break; |
| case SIGSTOP: name = "SIGSTOP"; break; |
| case SIGSYS: name = "SIGSYS"; break; |
| case SIGTERM: name = "SIGTERM"; break; |
| case SIGTRAP: name = "SIGTRAP"; break; |
| case SIGTSTP: name = "SIGTSTP"; break; |
| case SIGTTIN: name = "SIGTTIN"; break; |
| case SIGTTOU: name = "SIGTTOU"; break; |
| case SIGURG: name = "SIGURG"; break; |
| case SIGUSR1: name = "SIGUSR1"; break; |
| case SIGUSR2: name = "SIGUSR2"; break; |
| case SIGVTALRM: name = "SIGVTALRM"; break; |
| case SIGWINCH: name = "SIGWINCH"; break; |
| case SIGXCPU: name = "SIGXCPU"; break; |
| case SIGXFSZ: name = "SIGXFSZ"; break; |
| default: name = "<Unknown signal>"; break; |
| } |
| // clang-format on |
| |
| if (include_number) { |
| std::ostringstream out; |
| out << name << " (" << signal << ")"; |
| return out.str(); |
| } |
| return name; |
| } |
| |
| uint64_t GetLdSoDebugRelativeAddress(const std::string& ld_so_path) { |
| auto elf = elflib::ElfLib::Create(ld_so_path); |
| if (!elf) |
| return 0; |
| |
| // LLDB and GDB have a list of different symbols to look for for different dynamic loaders. |
| // We're only currently concerned with the one loader so omit that for now. |
| if (auto* sym = elf->GetDynamicSymbol("_r_debug")) |
| return sym->st_value; |
| |
| return 0; |
| } |
| |
| uint64_t GetLdSoBreakpointRelativeAddress(const std::string& ld_so_path) { |
| auto elf = elflib::ElfLib::Create(ld_so_path); |
| if (!elf) |
| return 0; |
| |
| // Different loaders use different symbol names. This is the list that everybody uses: |
| static const char* kDebugStateNames[] = { |
| "_dl_debug_state", "__dl_rtld_db_dlactivity", "_r_debug_state", |
| "r_debug_state", "rtld_db_dlactivity", "_rtld_debug_state", |
| }; |
| |
| for (const char* name : kDebugStateNames) { |
| if (auto* sym = elf->GetDynamicSymbol(name)) |
| return sym->st_value; |
| } |
| |
| return 0; |
| } |
| |
| } // namespace linux |
| } // namespace debug_agent |