| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/ptrace.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <atomic> |
| #include <csignal> |
| #include <cstddef> |
| #include <cstdio> |
| #include <cstring> |
| #include <memory> |
| #include <regex> |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <unwindstack/MapInfo.h> |
| #include <unwindstack/Maps.h> |
| #include <unwindstack/Regs.h> |
| #include <unwindstack/Unwinder.h> |
| |
| #include <android-base/file.h> |
| #include <android-base/stringprintf.h> |
| #include <procinfo/process.h> |
| |
| #include "ProcessTracer.h" |
| |
| namespace unwindstack { |
| |
| ProcessTracer::ProcessTracer(pid_t pid, bool is_tracing_threads) |
| : pid_(pid), is_tracing_threads_(is_tracing_threads) { |
| if (is_tracing_threads_) is_tracing_threads_ = InitProcessTids(); |
| } |
| |
| bool ProcessTracer::InitProcessTids() { |
| std::string error_msg; |
| if (!android::procinfo::GetProcessTids(pid_, &tids_, &error_msg)) { |
| fprintf(stderr, |
| "Failed to get process tids: %s. Reverting to tracing the " |
| "main thread only.\n", |
| error_msg.c_str()); |
| return false; |
| } |
| if (tids_.erase(pid_) != 1) { |
| fprintf(stderr, |
| "Failed to erase the main thread from the thread id set. " |
| "Reverting to tracing the main thread only.\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| ProcessTracer::~ProcessTracer() { |
| if (cur_attached_tid_ != kNoThreadAttached) Detach(cur_attached_tid_); |
| if (!is_running_) Resume(); |
| } |
| |
| bool ProcessTracer::Stop() { |
| if (kill(pid_, SIGSTOP) == kKillFailed) { |
| fprintf(stderr, "Failed to send stop signal to pid %d: %s\n", pid_, strerror(errno)); |
| return false; |
| } |
| usleep(1000); // 1 ms. Without this sleep, any attempt to resume right away may fail. |
| |
| is_running_ = false; |
| return true; |
| } |
| |
| bool ProcessTracer::Resume() { |
| if (kill(pid_, SIGCONT) == kKillFailed) { |
| fprintf(stderr, "Failed to send continue signal to pid %d: %s\n", pid_, strerror(errno)); |
| return false; |
| } |
| usleep(1000); // 1 ms. Without this sleep, any attempt to stop right away may fail. |
| |
| is_running_ = true; |
| return true; |
| } |
| |
| bool ProcessTracer::Detach(pid_t tid) { |
| if (tid != pid_ && tids_.find(tid) == tids_.end()) { |
| fprintf(stderr, "Tid %d does not belong to proc %d.\n", tid, pid_); |
| return false; |
| } |
| |
| if (cur_attached_tid_ == kNoThreadAttached) { |
| fprintf(stderr, "Cannot detach because no thread is currently attached.\n"); |
| return false; |
| } |
| if (is_running_ && !Stop()) return false; |
| |
| if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr) == kPtraceFailed) { |
| fprintf(stderr, "Failed to detach from tid %d: %s\n", tid, strerror(errno)); |
| return false; |
| } |
| |
| cur_attached_tid_ = kNoThreadAttached; |
| return true; |
| } |
| |
| bool ProcessTracer::Attach(pid_t tid) { |
| if (tid != pid_ && tids_.find(tid) == tids_.end()) { |
| fprintf(stderr, "Tid %d does not belong to proc %d.\n", tid, pid_); |
| return false; |
| } |
| |
| if (is_running_) Stop(); |
| if (cur_attached_tid_ != kNoThreadAttached) { |
| fprintf(stderr, "Cannot attatch to tid %d. Already attached to tid %d.\n", tid, |
| cur_attached_tid_); |
| return false; |
| } |
| |
| if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr) == kPtraceFailed) { |
| fprintf(stderr, "Failed to attached to tid %d: %s\n", tid, strerror(errno)); |
| return false; |
| } |
| int status; |
| if (waitpid(tid, &status, 0) == kWaitpidFailed) { |
| fprintf(stderr, "Failed to stop tid %d: %s\n", tid, strerror(errno)); |
| return false; |
| } |
| |
| cur_attached_tid_ = tid; |
| return true; |
| } |
| |
| bool ProcessTracer::StopInDesiredElf(const std::string& elf_name) { |
| signal(SIGINT, [](int) { keepWaitingForPcInElf = false; }); |
| bool pc_in_desired_elf = true; |
| do { |
| if (!Attach(pid_)) return false; |
| pc_in_desired_elf = ProcIsInDesiredElf(pid_, elf_name); |
| if (!Detach(pid_)) return false; |
| |
| if (!pc_in_desired_elf) { |
| for (pid_t tid : tids_) { |
| if (!Attach(tid)) return false; |
| pc_in_desired_elf = ProcIsInDesiredElf(tid, elf_name); |
| if (!Detach(tid)) return false; |
| if (pc_in_desired_elf) break; |
| } |
| } |
| |
| // If the process is not in the desired ELF, resume it for a short time, then check again. |
| if (!pc_in_desired_elf) { |
| Resume(); |
| usleep(1000); // 1 ms |
| Stop(); |
| } |
| } while (!pc_in_desired_elf && keepWaitingForPcInElf); |
| |
| if (!pc_in_desired_elf) { |
| fprintf(stderr, "\nExited while waiting for pid %d to enter %s.\n", pid_, elf_name.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ProcessTracer::UsesSharedLibrary(pid_t pid, const std::string& desired_elf_name) { |
| std::unique_ptr<Maps> maps = std::make_unique<RemoteMaps>(pid); |
| if (!maps->Parse()) { |
| fprintf(stderr, "Could not parse maps for pid %d.\n", pid); |
| return false; |
| } |
| for (const auto& map : *maps) { |
| if (android::base::Basename(map->name()).c_str() == desired_elf_name) return true; |
| } |
| return false; |
| } |
| |
| bool ProcessTracer::ProcIsInDesiredElf(pid_t pid, const std::string& desired_elf_name) { |
| std::unique_ptr<Regs> regs(Regs::RemoteGet(pid)); |
| if (regs == nullptr) { |
| fprintf(stderr, "Unable to get remote reg data.\n"); |
| return false; |
| } |
| UnwinderFromPid unwinder(1024, pid); |
| unwinder.SetRegs(regs.get()); |
| if (!unwinder.Init()) { |
| fprintf(stderr, "Unable to intitialize unwinder.\n"); |
| return false; |
| } |
| Maps* maps = unwinder.GetMaps(); |
| auto map_info = maps->Find(regs->pc()); |
| if (map_info == nullptr) { |
| regs->fallback_pc(); |
| map_info = maps->Find(regs->pc()); |
| if (map_info == nullptr) { |
| return false; |
| } |
| } |
| |
| const std::string& current_elf_name = android::base::Basename(map_info->name()).c_str(); |
| bool in_desired_elf = current_elf_name == desired_elf_name; |
| if (in_desired_elf) printf("pid %d is in %s! Unwinding...\n\n", pid, desired_elf_name.c_str()); |
| return in_desired_elf; |
| } |
| } // namespace unwindstack |