| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright (C) 2016 The Android Open Source Project | 
 | 3 |  * | 
 | 4 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 | 5 |  * you may not use this file except in compliance with the License. | 
 | 6 |  * You may obtain a copy of the License at | 
 | 7 |  * | 
 | 8 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 | 9 |  * | 
 | 10 |  * Unless required by applicable law or agreed to in writing, software | 
 | 11 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 | 12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | 13 |  * See the License for the specific language governing permissions and | 
 | 14 |  * limitations under the License. | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | #include "ThreadCapture.h" | 
 | 18 |  | 
 | 19 | #include <elf.h> | 
 | 20 | #include <errno.h> | 
 | 21 | #include <fcntl.h> | 
 | 22 | #include <limits.h> | 
 | 23 | #include <stdlib.h> | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 24 | #include <sys/ptrace.h> | 
 | 25 | #include <sys/stat.h> | 
 | 26 | #include <sys/syscall.h> | 
 | 27 | #include <sys/types.h> | 
 | 28 | #include <sys/uio.h> | 
 | 29 | #include <sys/wait.h> | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 30 | #include <unistd.h> | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 31 |  | 
 | 32 | #include <map> | 
 | 33 | #include <memory> | 
 | 34 | #include <set> | 
 | 35 | #include <vector> | 
 | 36 |  | 
 | 37 | #include <android-base/unique_fd.h> | 
 | 38 |  | 
 | 39 | #include "Allocator.h" | 
 | 40 | #include "log.h" | 
 | 41 |  | 
| Colin Cross | a9939e9 | 2017-06-21 13:13:00 -0700 | [diff] [blame] | 42 | namespace android { | 
 | 43 |  | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 44 | // bionic interfaces used: | 
 | 45 | // atoi | 
 | 46 | // strlcat | 
 | 47 | // writev | 
 | 48 |  | 
 | 49 | // bionic interfaces reimplemented to avoid allocation: | 
 | 50 | // getdents64 | 
 | 51 |  | 
 | 52 | // Convert a pid > 0 to a string.  sprintf might allocate, so we can't use it. | 
 | 53 | // Returns a pointer somewhere in buf to a null terminated string, or NULL | 
 | 54 | // on error. | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 55 | static char* pid_to_str(char* buf, size_t len, pid_t pid) { | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 56 |   if (pid <= 0) { | 
 | 57 |     return nullptr; | 
 | 58 |   } | 
 | 59 |  | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 60 |   char* ptr = buf + len - 1; | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 61 |   *ptr = 0; | 
 | 62 |   while (pid > 0) { | 
 | 63 |     ptr--; | 
 | 64 |     if (ptr < buf) { | 
 | 65 |       return nullptr; | 
 | 66 |     } | 
 | 67 |     *ptr = '0' + (pid % 10); | 
 | 68 |     pid /= 10; | 
 | 69 |   } | 
 | 70 |  | 
 | 71 |   return ptr; | 
 | 72 | } | 
 | 73 |  | 
 | 74 | class ThreadCaptureImpl { | 
 | 75 |  public: | 
 | 76 |   ThreadCaptureImpl(pid_t pid, Allocator<ThreadCaptureImpl>& allocator); | 
 | 77 |   ~ThreadCaptureImpl() {} | 
 | 78 |   bool ListThreads(TidList& tids); | 
 | 79 |   bool CaptureThreads(); | 
 | 80 |   bool ReleaseThreads(); | 
 | 81 |   bool ReleaseThread(pid_t tid); | 
 | 82 |   bool CapturedThreadInfo(ThreadInfoList& threads); | 
 | 83 |   void InjectTestFunc(std::function<void(pid_t)>&& f) { inject_test_func_ = f; } | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 84 |  | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 85 |  private: | 
 | 86 |   int CaptureThread(pid_t tid); | 
 | 87 |   bool ReleaseThread(pid_t tid, unsigned int signal); | 
 | 88 |   int PtraceAttach(pid_t tid); | 
 | 89 |   void PtraceDetach(pid_t tid, unsigned int signal); | 
 | 90 |   bool PtraceThreadInfo(pid_t tid, ThreadInfo& thread_info); | 
 | 91 |  | 
| Colin Cross | e4cbe0e | 2016-03-04 16:34:42 -0800 | [diff] [blame] | 92 |   allocator::map<pid_t, unsigned int> captured_threads_; | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 93 |   Allocator<ThreadCaptureImpl> allocator_; | 
 | 94 |   pid_t pid_; | 
 | 95 |   std::function<void(pid_t)> inject_test_func_; | 
 | 96 | }; | 
 | 97 |  | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 98 | ThreadCaptureImpl::ThreadCaptureImpl(pid_t pid, Allocator<ThreadCaptureImpl>& allocator) | 
 | 99 |     : captured_threads_(allocator), allocator_(allocator), pid_(pid) {} | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 100 |  | 
 | 101 | bool ThreadCaptureImpl::ListThreads(TidList& tids) { | 
 | 102 |   tids.clear(); | 
 | 103 |  | 
 | 104 |   char pid_buf[11]; | 
 | 105 |   char path[256] = "/proc/"; | 
 | 106 |   char* pid_str = pid_to_str(pid_buf, sizeof(pid_buf), pid_); | 
 | 107 |   if (!pid_str) { | 
 | 108 |     return false; | 
 | 109 |   } | 
 | 110 |   strlcat(path, pid_str, sizeof(path)); | 
 | 111 |   strlcat(path, "/task", sizeof(path)); | 
 | 112 |  | 
| Elliott Hughes | 2c5d1d7 | 2016-03-28 12:15:36 -0700 | [diff] [blame] | 113 |   android::base::unique_fd fd(open(path, O_CLOEXEC | O_DIRECTORY | O_RDONLY)); | 
 | 114 |   if (fd == -1) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 115 |     MEM_ALOGE("failed to open %s: %s", path, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 116 |     return false; | 
 | 117 |   } | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 118 |  | 
 | 119 |   struct linux_dirent64 { | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 120 |     uint64_t d_ino; | 
 | 121 |     int64_t d_off; | 
 | 122 |     uint16_t d_reclen; | 
 | 123 |     char d_type; | 
 | 124 |     char d_name[]; | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 125 |   } __attribute((packed)); | 
 | 126 |   char dirent_buf[4096]; | 
 | 127 |   ssize_t nread; | 
 | 128 |   do { | 
| Elliott Hughes | 2c5d1d7 | 2016-03-28 12:15:36 -0700 | [diff] [blame] | 129 |     nread = syscall(SYS_getdents64, fd.get(), dirent_buf, sizeof(dirent_buf)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 130 |     if (nread < 0) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 131 |       MEM_ALOGE("failed to get directory entries from %s: %s", path, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 132 |       return false; | 
 | 133 |     } else if (nread > 0) { | 
 | 134 |       ssize_t off = 0; | 
 | 135 |       while (off < nread) { | 
 | 136 |         linux_dirent64* dirent = reinterpret_cast<linux_dirent64*>(dirent_buf + off); | 
 | 137 |         off += dirent->d_reclen; | 
 | 138 |         pid_t tid = atoi(dirent->d_name); | 
 | 139 |         if (tid <= 0) { | 
 | 140 |           continue; | 
 | 141 |         } | 
 | 142 |         tids.push_back(tid); | 
 | 143 |       } | 
 | 144 |     } | 
 | 145 |  | 
 | 146 |   } while (nread != 0); | 
 | 147 |  | 
 | 148 |   return true; | 
 | 149 | } | 
 | 150 |  | 
 | 151 | bool ThreadCaptureImpl::CaptureThreads() { | 
 | 152 |   TidList tids{allocator_}; | 
 | 153 |  | 
 | 154 |   bool found_new_thread; | 
 | 155 |   do { | 
 | 156 |     if (!ListThreads(tids)) { | 
 | 157 |       ReleaseThreads(); | 
 | 158 |       return false; | 
 | 159 |     } | 
 | 160 |  | 
 | 161 |     found_new_thread = false; | 
 | 162 |  | 
 | 163 |     for (auto it = tids.begin(); it != tids.end(); it++) { | 
 | 164 |       auto captured = captured_threads_.find(*it); | 
 | 165 |       if (captured == captured_threads_.end()) { | 
 | 166 |         if (CaptureThread(*it) < 0) { | 
 | 167 |           ReleaseThreads(); | 
 | 168 |           return false; | 
 | 169 |         } | 
 | 170 |         found_new_thread = true; | 
 | 171 |       } | 
 | 172 |     } | 
 | 173 |   } while (found_new_thread); | 
 | 174 |  | 
 | 175 |   return true; | 
 | 176 | } | 
 | 177 |  | 
 | 178 | // Detatches from a thread, delivering signal if nonzero, logs on error | 
 | 179 | void ThreadCaptureImpl::PtraceDetach(pid_t tid, unsigned int signal) { | 
 | 180 |   void* sig_ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(signal)); | 
 | 181 |   if (ptrace(PTRACE_DETACH, tid, NULL, sig_ptr) < 0 && errno != ESRCH) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 182 |     MEM_ALOGE("failed to detach from thread %d of process %d: %s", tid, pid_, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 183 |   } | 
 | 184 | } | 
 | 185 |  | 
 | 186 | // Attaches to and pauses thread. | 
 | 187 | // Returns 1 on attach, 0 on tid not found, -1 and logs on error | 
 | 188 | int ThreadCaptureImpl::PtraceAttach(pid_t tid) { | 
 | 189 |   int ret = ptrace(PTRACE_SEIZE, tid, NULL, NULL); | 
 | 190 |   if (ret < 0) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 191 |     MEM_ALOGE("failed to attach to thread %d of process %d: %s", tid, pid_, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 192 |     return -1; | 
 | 193 |   } | 
 | 194 |  | 
 | 195 |   if (inject_test_func_) { | 
 | 196 |     inject_test_func_(tid); | 
 | 197 |   } | 
 | 198 |  | 
 | 199 |   if (ptrace(PTRACE_INTERRUPT, tid, 0, 0) < 0) { | 
 | 200 |     if (errno == ESRCH) { | 
 | 201 |       return 0; | 
 | 202 |     } else { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 203 |       MEM_ALOGE("failed to interrupt thread %d of process %d: %s", tid, pid_, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 204 |       PtraceDetach(tid, 0); | 
 | 205 |       return -1; | 
 | 206 |     } | 
 | 207 |   } | 
 | 208 |   return 1; | 
 | 209 | } | 
 | 210 |  | 
 | 211 | bool ThreadCaptureImpl::PtraceThreadInfo(pid_t tid, ThreadInfo& thread_info) { | 
 | 212 |   thread_info.tid = tid; | 
 | 213 |  | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 214 |   const unsigned int max_num_regs = 128;  // larger than number of registers on any device | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 215 |   uintptr_t regs[max_num_regs]; | 
 | 216 |   struct iovec iovec; | 
 | 217 |   iovec.iov_base = ®s; | 
 | 218 |   iovec.iov_len = sizeof(regs); | 
 | 219 |  | 
 | 220 |   if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRSTATUS), &iovec)) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 221 |     MEM_ALOGE("ptrace getregset for thread %d of process %d failed: %s", tid, pid_, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 222 |     return false; | 
 | 223 |   } | 
 | 224 |  | 
 | 225 |   unsigned int num_regs = iovec.iov_len / sizeof(uintptr_t); | 
 | 226 |   thread_info.regs.assign(®s[0], ®s[num_regs]); | 
 | 227 |  | 
 | 228 |   const int sp = | 
 | 229 | #if defined(__x86_64__) | 
 | 230 |       offsetof(struct pt_regs, rsp) / sizeof(uintptr_t) | 
 | 231 | #elif defined(__i386__) | 
 | 232 |       offsetof(struct pt_regs, esp) / sizeof(uintptr_t) | 
 | 233 | #elif defined(__arm__) | 
 | 234 |       offsetof(struct pt_regs, ARM_sp) / sizeof(uintptr_t) | 
 | 235 | #elif defined(__aarch64__) | 
 | 236 |       offsetof(struct user_pt_regs, sp) / sizeof(uintptr_t) | 
 | 237 | #elif defined(__mips__) || defined(__mips64__) | 
 | 238 |       offsetof(struct pt_regs, regs[29]) / sizeof(uintptr_t) | 
 | 239 | #else | 
 | 240 | #error Unrecognized architecture | 
 | 241 | #endif | 
 | 242 |       ; | 
 | 243 |  | 
 | 244 |   // TODO(ccross): use /proc/tid/status or /proc/pid/maps to get start_stack | 
 | 245 |  | 
 | 246 |   thread_info.stack = std::pair<uintptr_t, uintptr_t>(regs[sp], 0); | 
 | 247 |  | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 248 |   return true; | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 249 | } | 
 | 250 |  | 
 | 251 | int ThreadCaptureImpl::CaptureThread(pid_t tid) { | 
 | 252 |   int ret = PtraceAttach(tid); | 
 | 253 |   if (ret <= 0) { | 
 | 254 |     return ret; | 
 | 255 |   } | 
 | 256 |  | 
 | 257 |   int status = 0; | 
 | 258 |   if (TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL)) < 0) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 259 |     MEM_ALOGE("failed to wait for pause of thread %d of process %d: %s", tid, pid_, strerror(errno)); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 260 |     PtraceDetach(tid, 0); | 
 | 261 |     return -1; | 
 | 262 |   } | 
 | 263 |  | 
 | 264 |   if (!WIFSTOPPED(status)) { | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 265 |     MEM_ALOGE("thread %d of process %d was not paused after waitpid, killed?", tid, pid_); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 266 |     return 0; | 
 | 267 |   } | 
 | 268 |  | 
 | 269 |   unsigned int resume_signal = 0; | 
 | 270 |  | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 271 |   unsigned int signal = WSTOPSIG(status); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 272 |   if ((status >> 16) == PTRACE_EVENT_STOP) { | 
 | 273 |     switch (signal) { | 
 | 274 |       case SIGSTOP: | 
 | 275 |       case SIGTSTP: | 
 | 276 |       case SIGTTIN: | 
 | 277 |       case SIGTTOU: | 
 | 278 |         // group-stop signals | 
 | 279 |         break; | 
 | 280 |       case SIGTRAP: | 
 | 281 |         // normal ptrace interrupt stop | 
 | 282 |         break; | 
 | 283 |       default: | 
| Christopher Ferris | 47dea71 | 2017-05-03 17:34:29 -0700 | [diff] [blame] | 284 |         MEM_ALOGE("unexpected signal %d with PTRACE_EVENT_STOP for thread %d of process %d", signal, | 
 | 285 |                   tid, pid_); | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 286 |         return -1; | 
 | 287 |     } | 
 | 288 |   } else { | 
 | 289 |     // signal-delivery-stop | 
 | 290 |     resume_signal = signal; | 
 | 291 |   } | 
 | 292 |  | 
 | 293 |   captured_threads_[tid] = resume_signal; | 
 | 294 |   return 1; | 
 | 295 | } | 
 | 296 |  | 
 | 297 | bool ThreadCaptureImpl::ReleaseThread(pid_t tid) { | 
 | 298 |   auto it = captured_threads_.find(tid); | 
 | 299 |   if (it == captured_threads_.end()) { | 
 | 300 |     return false; | 
 | 301 |   } | 
 | 302 |   return ReleaseThread(it->first, it->second); | 
 | 303 | } | 
 | 304 |  | 
 | 305 | bool ThreadCaptureImpl::ReleaseThread(pid_t tid, unsigned int signal) { | 
 | 306 |   PtraceDetach(tid, signal); | 
 | 307 |   return true; | 
 | 308 | } | 
 | 309 |  | 
 | 310 | bool ThreadCaptureImpl::ReleaseThreads() { | 
 | 311 |   bool ret = true; | 
| Colin Cross | a83881e | 2017-06-22 10:50:05 -0700 | [diff] [blame] | 312 |   for (auto it = captured_threads_.begin(); it != captured_threads_.end();) { | 
| Colin Cross | bcb4ed3 | 2016-01-14 15:35:40 -0800 | [diff] [blame] | 313 |     if (ReleaseThread(it->first, it->second)) { | 
 | 314 |       it = captured_threads_.erase(it); | 
 | 315 |     } else { | 
 | 316 |       it++; | 
 | 317 |       ret = false; | 
 | 318 |     } | 
 | 319 |   } | 
 | 320 |   return ret; | 
 | 321 | } | 
 | 322 |  | 
 | 323 | bool ThreadCaptureImpl::CapturedThreadInfo(ThreadInfoList& threads) { | 
 | 324 |   threads.clear(); | 
 | 325 |  | 
 | 326 |   for (auto it = captured_threads_.begin(); it != captured_threads_.end(); it++) { | 
 | 327 |     ThreadInfo t{0, allocator::vector<uintptr_t>(allocator_), std::pair<uintptr_t, uintptr_t>(0, 0)}; | 
 | 328 |     if (!PtraceThreadInfo(it->first, t)) { | 
 | 329 |       return false; | 
 | 330 |     } | 
 | 331 |     threads.push_back(t); | 
 | 332 |   } | 
 | 333 |   return true; | 
 | 334 | } | 
 | 335 |  | 
 | 336 | ThreadCapture::ThreadCapture(pid_t pid, Allocator<ThreadCapture> allocator) { | 
 | 337 |   Allocator<ThreadCaptureImpl> impl_allocator = allocator; | 
 | 338 |   impl_ = impl_allocator.make_unique(pid, impl_allocator); | 
 | 339 | } | 
 | 340 |  | 
 | 341 | ThreadCapture::~ThreadCapture() {} | 
 | 342 |  | 
 | 343 | bool ThreadCapture::ListThreads(TidList& tids) { | 
 | 344 |   return impl_->ListThreads(tids); | 
 | 345 | } | 
 | 346 |  | 
 | 347 | bool ThreadCapture::CaptureThreads() { | 
 | 348 |   return impl_->CaptureThreads(); | 
 | 349 | } | 
 | 350 |  | 
 | 351 | bool ThreadCapture::ReleaseThreads() { | 
 | 352 |   return impl_->ReleaseThreads(); | 
 | 353 | } | 
 | 354 |  | 
 | 355 | bool ThreadCapture::ReleaseThread(pid_t tid) { | 
 | 356 |   return impl_->ReleaseThread(tid); | 
 | 357 | } | 
 | 358 |  | 
 | 359 | bool ThreadCapture::CapturedThreadInfo(ThreadInfoList& threads) { | 
 | 360 |   return impl_->CapturedThreadInfo(threads); | 
 | 361 | } | 
 | 362 |  | 
 | 363 | void ThreadCapture::InjectTestFunc(std::function<void(pid_t)>&& f) { | 
 | 364 |   impl_->InjectTestFunc(std::forward<std::function<void(pid_t)>>(f)); | 
 | 365 | } | 
| Colin Cross | a9939e9 | 2017-06-21 13:13:00 -0700 | [diff] [blame] | 366 |  | 
 | 367 | }  // namespace android |