Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | // Copyright (c) 2010 Google Inc. |
michael@0 | 2 | // All rights reserved. |
michael@0 | 3 | // |
michael@0 | 4 | // Redistribution and use in source and binary forms, with or without |
michael@0 | 5 | // modification, are permitted provided that the following conditions are |
michael@0 | 6 | // met: |
michael@0 | 7 | // |
michael@0 | 8 | // * Redistributions of source code must retain the above copyright |
michael@0 | 9 | // notice, this list of conditions and the following disclaimer. |
michael@0 | 10 | // * Redistributions in binary form must reproduce the above |
michael@0 | 11 | // copyright notice, this list of conditions and the following disclaimer |
michael@0 | 12 | // in the documentation and/or other materials provided with the |
michael@0 | 13 | // distribution. |
michael@0 | 14 | // * Neither the name of Google Inc. nor the names of its |
michael@0 | 15 | // contributors may be used to endorse or promote products derived from |
michael@0 | 16 | // this software without specific prior written permission. |
michael@0 | 17 | // |
michael@0 | 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
michael@0 | 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
michael@0 | 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
michael@0 | 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
michael@0 | 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
michael@0 | 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
michael@0 | 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
michael@0 | 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
michael@0 | 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
michael@0 | 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 29 | |
michael@0 | 30 | // The ExceptionHandler object installs signal handlers for a number of |
michael@0 | 31 | // signals. We rely on the signal handler running on the thread which crashed |
michael@0 | 32 | // in order to identify it. This is true of the synchronous signals (SEGV etc), |
michael@0 | 33 | // but not true of ABRT. Thus, if you send ABRT to yourself in a program which |
michael@0 | 34 | // uses ExceptionHandler, you need to use tgkill to direct it to the current |
michael@0 | 35 | // thread. |
michael@0 | 36 | // |
michael@0 | 37 | // The signal flow looks like this: |
michael@0 | 38 | // |
michael@0 | 39 | // SignalHandler (uses a global stack of ExceptionHandler objects to find |
michael@0 | 40 | // | one to handle the signal. If the first rejects it, try |
michael@0 | 41 | // | the second etc...) |
michael@0 | 42 | // V |
michael@0 | 43 | // HandleSignal ----------------------------| (clones a new process which |
michael@0 | 44 | // | | shares an address space with |
michael@0 | 45 | // (wait for cloned | the crashed process. This |
michael@0 | 46 | // process) | allows us to ptrace the crashed |
michael@0 | 47 | // | | process) |
michael@0 | 48 | // V V |
michael@0 | 49 | // (set signal handler to ThreadEntry (static function to bounce |
michael@0 | 50 | // SIG_DFL and rethrow, | back into the object) |
michael@0 | 51 | // killing the crashed | |
michael@0 | 52 | // process) V |
michael@0 | 53 | // DoDump (writes minidump) |
michael@0 | 54 | // | |
michael@0 | 55 | // V |
michael@0 | 56 | // sys_exit |
michael@0 | 57 | // |
michael@0 | 58 | |
michael@0 | 59 | // This code is a little fragmented. Different functions of the ExceptionHandler |
michael@0 | 60 | // class run in a number of different contexts. Some of them run in a normal |
michael@0 | 61 | // context and are easy to code, others run in a compromised context and the |
michael@0 | 62 | // restrictions at the top of minidump_writer.cc apply: no libc and use the |
michael@0 | 63 | // alternative malloc. Each function should have comment above it detailing the |
michael@0 | 64 | // context which it runs in. |
michael@0 | 65 | |
michael@0 | 66 | #include "client/linux/handler/exception_handler.h" |
michael@0 | 67 | |
michael@0 | 68 | #include <errno.h> |
michael@0 | 69 | #include <fcntl.h> |
michael@0 | 70 | #include <linux/limits.h> |
michael@0 | 71 | #include <sched.h> |
michael@0 | 72 | #include <signal.h> |
michael@0 | 73 | #include <stdio.h> |
michael@0 | 74 | #include <sys/mman.h> |
michael@0 | 75 | #include <sys/prctl.h> |
michael@0 | 76 | #include <sys/syscall.h> |
michael@0 | 77 | #include <sys/wait.h> |
michael@0 | 78 | #include <unistd.h> |
michael@0 | 79 | |
michael@0 | 80 | #include <sys/signal.h> |
michael@0 | 81 | #include <sys/ucontext.h> |
michael@0 | 82 | #include <sys/user.h> |
michael@0 | 83 | #include <ucontext.h> |
michael@0 | 84 | |
michael@0 | 85 | #include <algorithm> |
michael@0 | 86 | #include <utility> |
michael@0 | 87 | #include <vector> |
michael@0 | 88 | |
michael@0 | 89 | #include "common/linux/linux_libc_support.h" |
michael@0 | 90 | #include "common/memory.h" |
michael@0 | 91 | #include "client/linux/log/log.h" |
michael@0 | 92 | #include "client/linux/minidump_writer/linux_dumper.h" |
michael@0 | 93 | #include "client/linux/minidump_writer/minidump_writer.h" |
michael@0 | 94 | #include "common/linux/eintr_wrapper.h" |
michael@0 | 95 | #include "third_party/lss/linux_syscall_support.h" |
michael@0 | 96 | |
michael@0 | 97 | #include "linux/sched.h" |
michael@0 | 98 | |
michael@0 | 99 | #ifndef PR_SET_PTRACER |
michael@0 | 100 | #define PR_SET_PTRACER 0x59616d61 |
michael@0 | 101 | #endif |
michael@0 | 102 | |
michael@0 | 103 | // A wrapper for the tgkill syscall: send a signal to a specific thread. |
michael@0 | 104 | static int tgkill(pid_t tgid, pid_t tid, int sig) { |
michael@0 | 105 | return syscall(__NR_tgkill, tgid, tid, sig); |
michael@0 | 106 | return 0; |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | namespace google_breakpad { |
michael@0 | 110 | |
michael@0 | 111 | namespace { |
michael@0 | 112 | // The list of signals which we consider to be crashes. The default action for |
michael@0 | 113 | // all these signals must be Core (see man 7 signal) because we rethrow the |
michael@0 | 114 | // signal after handling it and expect that it'll be fatal. |
michael@0 | 115 | const int kExceptionSignals[] = { |
michael@0 | 116 | SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS |
michael@0 | 117 | }; |
michael@0 | 118 | const int kNumHandledSignals = |
michael@0 | 119 | sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]); |
michael@0 | 120 | struct sigaction old_handlers[kNumHandledSignals]; |
michael@0 | 121 | bool handlers_installed = false; |
michael@0 | 122 | |
michael@0 | 123 | // InstallAlternateStackLocked will store the newly installed stack in new_stack |
michael@0 | 124 | // and (if it exists) the previously installed stack in old_stack. |
michael@0 | 125 | stack_t old_stack; |
michael@0 | 126 | stack_t new_stack; |
michael@0 | 127 | bool stack_installed = false; |
michael@0 | 128 | |
michael@0 | 129 | // Create an alternative stack to run the signal handlers on. This is done since |
michael@0 | 130 | // the signal might have been caused by a stack overflow. |
michael@0 | 131 | // Runs before crashing: normal context. |
michael@0 | 132 | void InstallAlternateStackLocked() { |
michael@0 | 133 | if (stack_installed) |
michael@0 | 134 | return; |
michael@0 | 135 | |
michael@0 | 136 | memset(&old_stack, 0, sizeof(old_stack)); |
michael@0 | 137 | memset(&new_stack, 0, sizeof(new_stack)); |
michael@0 | 138 | |
michael@0 | 139 | // SIGSTKSZ may be too small to prevent the signal handlers from overrunning |
michael@0 | 140 | // the alternative stack. Ensure that the size of the alternative stack is |
michael@0 | 141 | // large enough. |
michael@0 | 142 | static const unsigned kSigStackSize = std::max(8192, SIGSTKSZ); |
michael@0 | 143 | |
michael@0 | 144 | // Only set an alternative stack if there isn't already one, or if the current |
michael@0 | 145 | // one is too small. |
michael@0 | 146 | if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp || |
michael@0 | 147 | old_stack.ss_size < kSigStackSize) { |
michael@0 | 148 | new_stack.ss_sp = malloc(kSigStackSize); |
michael@0 | 149 | new_stack.ss_size = kSigStackSize; |
michael@0 | 150 | |
michael@0 | 151 | if (sys_sigaltstack(&new_stack, NULL) == -1) { |
michael@0 | 152 | free(new_stack.ss_sp); |
michael@0 | 153 | return; |
michael@0 | 154 | } |
michael@0 | 155 | stack_installed = true; |
michael@0 | 156 | } |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | // Runs before crashing: normal context. |
michael@0 | 160 | void RestoreAlternateStackLocked() { |
michael@0 | 161 | if (!stack_installed) |
michael@0 | 162 | return; |
michael@0 | 163 | |
michael@0 | 164 | stack_t current_stack; |
michael@0 | 165 | if (sys_sigaltstack(NULL, ¤t_stack) == -1) |
michael@0 | 166 | return; |
michael@0 | 167 | |
michael@0 | 168 | // Only restore the old_stack if the current alternative stack is the one |
michael@0 | 169 | // installed by the call to InstallAlternateStackLocked. |
michael@0 | 170 | if (current_stack.ss_sp == new_stack.ss_sp) { |
michael@0 | 171 | if (old_stack.ss_sp) { |
michael@0 | 172 | if (sys_sigaltstack(&old_stack, NULL) == -1) |
michael@0 | 173 | return; |
michael@0 | 174 | } else { |
michael@0 | 175 | stack_t disable_stack; |
michael@0 | 176 | disable_stack.ss_flags = SS_DISABLE; |
michael@0 | 177 | if (sys_sigaltstack(&disable_stack, NULL) == -1) |
michael@0 | 178 | return; |
michael@0 | 179 | } |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | free(new_stack.ss_sp); |
michael@0 | 183 | stack_installed = false; |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | } // namespace |
michael@0 | 187 | |
michael@0 | 188 | // We can stack multiple exception handlers. In that case, this is the global |
michael@0 | 189 | // which holds the stack. |
michael@0 | 190 | std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; |
michael@0 | 191 | pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = |
michael@0 | 192 | PTHREAD_MUTEX_INITIALIZER; |
michael@0 | 193 | |
michael@0 | 194 | // Runs before crashing: normal context. |
michael@0 | 195 | ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor, |
michael@0 | 196 | FilterCallback filter, |
michael@0 | 197 | MinidumpCallback callback, |
michael@0 | 198 | void* callback_context, |
michael@0 | 199 | bool install_handler, |
michael@0 | 200 | const int server_fd) |
michael@0 | 201 | : filter_(filter), |
michael@0 | 202 | callback_(callback), |
michael@0 | 203 | callback_context_(callback_context), |
michael@0 | 204 | minidump_descriptor_(descriptor), |
michael@0 | 205 | crash_handler_(NULL) { |
michael@0 | 206 | if (server_fd >= 0) |
michael@0 | 207 | crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd)); |
michael@0 | 208 | |
michael@0 | 209 | if (!IsOutOfProcess() && !minidump_descriptor_.IsFD()) |
michael@0 | 210 | minidump_descriptor_.UpdatePath(); |
michael@0 | 211 | |
michael@0 | 212 | pthread_mutex_lock(&handler_stack_mutex_); |
michael@0 | 213 | if (!handler_stack_) |
michael@0 | 214 | handler_stack_ = new std::vector<ExceptionHandler*>; |
michael@0 | 215 | if (install_handler) { |
michael@0 | 216 | InstallAlternateStackLocked(); |
michael@0 | 217 | InstallHandlersLocked(); |
michael@0 | 218 | } |
michael@0 | 219 | handler_stack_->push_back(this); |
michael@0 | 220 | pthread_mutex_unlock(&handler_stack_mutex_); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | // Runs before crashing: normal context. |
michael@0 | 224 | ExceptionHandler::~ExceptionHandler() { |
michael@0 | 225 | pthread_mutex_lock(&handler_stack_mutex_); |
michael@0 | 226 | std::vector<ExceptionHandler*>::iterator handler = |
michael@0 | 227 | std::find(handler_stack_->begin(), handler_stack_->end(), this); |
michael@0 | 228 | handler_stack_->erase(handler); |
michael@0 | 229 | if (handler_stack_->empty()) { |
michael@0 | 230 | RestoreAlternateStackLocked(); |
michael@0 | 231 | RestoreHandlersLocked(); |
michael@0 | 232 | } |
michael@0 | 233 | pthread_mutex_unlock(&handler_stack_mutex_); |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | // Runs before crashing: normal context. |
michael@0 | 237 | // static |
michael@0 | 238 | bool ExceptionHandler::InstallHandlersLocked() { |
michael@0 | 239 | if (handlers_installed) |
michael@0 | 240 | return false; |
michael@0 | 241 | |
michael@0 | 242 | // Fail if unable to store all the old handlers. |
michael@0 | 243 | for (int i = 0; i < kNumHandledSignals; ++i) { |
michael@0 | 244 | if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1) |
michael@0 | 245 | return false; |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | struct sigaction sa; |
michael@0 | 249 | memset(&sa, 0, sizeof(sa)); |
michael@0 | 250 | sigemptyset(&sa.sa_mask); |
michael@0 | 251 | |
michael@0 | 252 | // Mask all exception signals when we're handling one of them. |
michael@0 | 253 | for (int i = 0; i < kNumHandledSignals; ++i) |
michael@0 | 254 | sigaddset(&sa.sa_mask, kExceptionSignals[i]); |
michael@0 | 255 | |
michael@0 | 256 | sa.sa_sigaction = SignalHandler; |
michael@0 | 257 | sa.sa_flags = SA_ONSTACK | SA_SIGINFO; |
michael@0 | 258 | |
michael@0 | 259 | for (int i = 0; i < kNumHandledSignals; ++i) { |
michael@0 | 260 | if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) { |
michael@0 | 261 | // At this point it is impractical to back out changes, and so failure to |
michael@0 | 262 | // install a signal is intentionally ignored. |
michael@0 | 263 | } |
michael@0 | 264 | } |
michael@0 | 265 | handlers_installed = true; |
michael@0 | 266 | return true; |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 270 | // Runs on the crashing thread. |
michael@0 | 271 | // static |
michael@0 | 272 | void ExceptionHandler::RestoreHandlersLocked() { |
michael@0 | 273 | if (!handlers_installed) |
michael@0 | 274 | return; |
michael@0 | 275 | |
michael@0 | 276 | for (int i = 0; i < kNumHandledSignals; ++i) { |
michael@0 | 277 | if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) { |
michael@0 | 278 | signal(kExceptionSignals[i], SIG_DFL); |
michael@0 | 279 | } |
michael@0 | 280 | } |
michael@0 | 281 | handlers_installed = false; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | // void ExceptionHandler::set_crash_handler(HandlerCallback callback) { |
michael@0 | 285 | // crash_handler_ = callback; |
michael@0 | 286 | // } |
michael@0 | 287 | |
michael@0 | 288 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 289 | // Runs on the crashing thread. |
michael@0 | 290 | // static |
michael@0 | 291 | void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { |
michael@0 | 292 | // All the exception signals are blocked at this point. |
michael@0 | 293 | pthread_mutex_lock(&handler_stack_mutex_); |
michael@0 | 294 | |
michael@0 | 295 | // Sometimes, Breakpad runs inside a process where some other buggy code |
michael@0 | 296 | // saves and restores signal handlers temporarily with 'signal' |
michael@0 | 297 | // instead of 'sigaction'. This loses the SA_SIGINFO flag associated |
michael@0 | 298 | // with this function. As a consequence, the values of 'info' and 'uc' |
michael@0 | 299 | // become totally bogus, generally inducing a crash. |
michael@0 | 300 | // |
michael@0 | 301 | // The following code tries to detect this case. When it does, it |
michael@0 | 302 | // resets the signal handlers with sigaction + SA_SIGINFO and returns. |
michael@0 | 303 | // This forces the signal to be thrown again, but this time the kernel |
michael@0 | 304 | // will call the function with the right arguments. |
michael@0 | 305 | struct sigaction cur_handler; |
michael@0 | 306 | if (sigaction(sig, NULL, &cur_handler) == 0 && |
michael@0 | 307 | (cur_handler.sa_flags & SA_SIGINFO) == 0) { |
michael@0 | 308 | // Reset signal handler with the right flags. |
michael@0 | 309 | sigemptyset(&cur_handler.sa_mask); |
michael@0 | 310 | sigaddset(&cur_handler.sa_mask, sig); |
michael@0 | 311 | |
michael@0 | 312 | cur_handler.sa_sigaction = SignalHandler; |
michael@0 | 313 | cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO; |
michael@0 | 314 | |
michael@0 | 315 | if (sigaction(sig, &cur_handler, NULL) == -1) { |
michael@0 | 316 | // When resetting the handler fails, try to reset the |
michael@0 | 317 | // default one to avoid an infinite loop here. |
michael@0 | 318 | signal(sig, SIG_DFL); |
michael@0 | 319 | } |
michael@0 | 320 | pthread_mutex_unlock(&handler_stack_mutex_); |
michael@0 | 321 | return; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | bool handled = false; |
michael@0 | 325 | for (int i = handler_stack_->size() - 1; !handled && i >= 0; --i) { |
michael@0 | 326 | handled = (*handler_stack_)[i]->HandleSignal(sig, info, uc); |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | // Upon returning from this signal handler, sig will become unmasked and then |
michael@0 | 330 | // it will be retriggered. If one of the ExceptionHandlers handled it |
michael@0 | 331 | // successfully, restore the default handler. Otherwise, restore the |
michael@0 | 332 | // previously installed handler. Then, when the signal is retriggered, it will |
michael@0 | 333 | // be delivered to the appropriate handler. |
michael@0 | 334 | if (handled) { |
michael@0 | 335 | signal(sig, SIG_DFL); |
michael@0 | 336 | } else { |
michael@0 | 337 | RestoreHandlersLocked(); |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | pthread_mutex_unlock(&handler_stack_mutex_); |
michael@0 | 341 | |
michael@0 | 342 | if (info->si_code <= 0) { |
michael@0 | 343 | // This signal was sent by another process. (Positive values of |
michael@0 | 344 | // si_code are reserved for kernel-originated signals.) In order |
michael@0 | 345 | // to retrigger it, we have to queue a new signal. |
michael@0 | 346 | if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) { |
michael@0 | 347 | // If we failed to kill ourselves (e.g. because a sandbox disallows us |
michael@0 | 348 | // to do so), we instead resort to terminating our process. This will |
michael@0 | 349 | // result in an incorrect exit code. |
michael@0 | 350 | _exit(1); |
michael@0 | 351 | } |
michael@0 | 352 | } else { |
michael@0 | 353 | // This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV). |
michael@0 | 354 | // No need to reissue the signal. It will automatically trigger again, |
michael@0 | 355 | // when we return from the signal handler. |
michael@0 | 356 | } |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | struct ThreadArgument { |
michael@0 | 360 | pid_t pid; // the crashing process |
michael@0 | 361 | const MinidumpDescriptor* minidump_descriptor; |
michael@0 | 362 | ExceptionHandler* handler; |
michael@0 | 363 | const void* context; // a CrashContext structure |
michael@0 | 364 | size_t context_size; |
michael@0 | 365 | }; |
michael@0 | 366 | |
michael@0 | 367 | // This is the entry function for the cloned process. We are in a compromised |
michael@0 | 368 | // context here: see the top of the file. |
michael@0 | 369 | // static |
michael@0 | 370 | int ExceptionHandler::ThreadEntry(void *arg) { |
michael@0 | 371 | const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); |
michael@0 | 372 | |
michael@0 | 373 | // Block here until the crashing process unblocks us when |
michael@0 | 374 | // we're allowed to use ptrace |
michael@0 | 375 | thread_arg->handler->WaitForContinueSignal(); |
michael@0 | 376 | |
michael@0 | 377 | return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, |
michael@0 | 378 | thread_arg->context_size) == false; |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 382 | // Runs on the crashing thread. |
michael@0 | 383 | bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { |
michael@0 | 384 | if (filter_ && !filter_(callback_context_)) |
michael@0 | 385 | return false; |
michael@0 | 386 | |
michael@0 | 387 | // Allow ourselves to be dumped if the signal is trusted. |
michael@0 | 388 | bool signal_trusted = info->si_code > 0; |
michael@0 | 389 | bool signal_pid_trusted = info->si_code == SI_USER || |
michael@0 | 390 | info->si_code == SI_TKILL; |
michael@0 | 391 | if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) { |
michael@0 | 392 | sys_prctl(PR_SET_DUMPABLE, 1); |
michael@0 | 393 | } |
michael@0 | 394 | CrashContext context; |
michael@0 | 395 | memcpy(&context.siginfo, info, sizeof(siginfo_t)); |
michael@0 | 396 | memcpy(&context.context, uc, sizeof(struct ucontext)); |
michael@0 | 397 | #if !defined(__ARM_EABI__) |
michael@0 | 398 | // FP state is not part of user ABI on ARM Linux. |
michael@0 | 399 | struct ucontext *uc_ptr = (struct ucontext*)uc; |
michael@0 | 400 | if (uc_ptr->uc_mcontext.fpregs) { |
michael@0 | 401 | memcpy(&context.float_state, |
michael@0 | 402 | uc_ptr->uc_mcontext.fpregs, |
michael@0 | 403 | sizeof(context.float_state)); |
michael@0 | 404 | } |
michael@0 | 405 | #endif |
michael@0 | 406 | context.tid = syscall(__NR_gettid); |
michael@0 | 407 | if (crash_handler_ != NULL) { |
michael@0 | 408 | if (crash_handler_(&context, sizeof(context), callback_context_)) { |
michael@0 | 409 | return true; |
michael@0 | 410 | } |
michael@0 | 411 | } |
michael@0 | 412 | return GenerateDump(&context); |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | // This is a public interface to HandleSignal that allows the client to |
michael@0 | 416 | // generate a crash dump. This function may run in a compromised context. |
michael@0 | 417 | bool ExceptionHandler::SimulateSignalDelivery(int sig) { |
michael@0 | 418 | siginfo_t siginfo = {}; |
michael@0 | 419 | // Mimic a trusted signal to allow tracing the process (see |
michael@0 | 420 | // ExceptionHandler::HandleSignal(). |
michael@0 | 421 | siginfo.si_code = SI_USER; |
michael@0 | 422 | siginfo.si_pid = getpid(); |
michael@0 | 423 | struct ucontext context; |
michael@0 | 424 | getcontext(&context); |
michael@0 | 425 | return HandleSignal(sig, &siginfo, &context); |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | // This function may run in a compromised context: see the top of the file. |
michael@0 | 429 | bool ExceptionHandler::GenerateDump(CrashContext *context) { |
michael@0 | 430 | if (IsOutOfProcess()) |
michael@0 | 431 | return crash_generation_client_->RequestDump(context, sizeof(*context)); |
michael@0 | 432 | |
michael@0 | 433 | static const unsigned kChildStackSize = 8000; |
michael@0 | 434 | PageAllocator allocator; |
michael@0 | 435 | uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize); |
michael@0 | 436 | if (!stack) |
michael@0 | 437 | return false; |
michael@0 | 438 | // clone() needs the top-most address. (scrub just to be safe) |
michael@0 | 439 | stack += kChildStackSize; |
michael@0 | 440 | my_memset(stack - 16, 0, 16); |
michael@0 | 441 | |
michael@0 | 442 | ThreadArgument thread_arg; |
michael@0 | 443 | thread_arg.handler = this; |
michael@0 | 444 | thread_arg.minidump_descriptor = &minidump_descriptor_; |
michael@0 | 445 | thread_arg.pid = getpid(); |
michael@0 | 446 | thread_arg.context = context; |
michael@0 | 447 | thread_arg.context_size = sizeof(*context); |
michael@0 | 448 | |
michael@0 | 449 | // We need to explicitly enable ptrace of parent processes on some |
michael@0 | 450 | // kernels, but we need to know the PID of the cloned process before we |
michael@0 | 451 | // can do this. Create a pipe here which we can use to block the |
michael@0 | 452 | // cloned process after creating it, until we have explicitly enabled ptrace |
michael@0 | 453 | if(sys_pipe(fdes) == -1) { |
michael@0 | 454 | // Creating the pipe failed. We'll log an error but carry on anyway, |
michael@0 | 455 | // as we'll probably still get a useful crash report. All that will happen |
michael@0 | 456 | // is the write() and read() calls will fail with EBADF |
michael@0 | 457 | static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump \ |
michael@0 | 458 | sys_pipe failed:"; |
michael@0 | 459 | logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1); |
michael@0 | 460 | logger::write(strerror(errno), strlen(strerror(errno))); |
michael@0 | 461 | logger::write("\n", 1); |
michael@0 | 462 | } |
michael@0 | 463 | |
michael@0 | 464 | const pid_t child = sys_clone( |
michael@0 | 465 | ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, |
michael@0 | 466 | &thread_arg, NULL, NULL, NULL); |
michael@0 | 467 | |
michael@0 | 468 | int r, status; |
michael@0 | 469 | // Allow the child to ptrace us |
michael@0 | 470 | sys_prctl(PR_SET_PTRACER, child); |
michael@0 | 471 | SendContinueSignalToChild(); |
michael@0 | 472 | do { |
michael@0 | 473 | r = sys_waitpid(child, &status, __WALL); |
michael@0 | 474 | } while (r == -1 && errno == EINTR); |
michael@0 | 475 | |
michael@0 | 476 | sys_close(fdes[0]); |
michael@0 | 477 | sys_close(fdes[1]); |
michael@0 | 478 | |
michael@0 | 479 | if (r == -1) { |
michael@0 | 480 | static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:"; |
michael@0 | 481 | logger::write(msg, sizeof(msg) - 1); |
michael@0 | 482 | logger::write(strerror(errno), strlen(strerror(errno))); |
michael@0 | 483 | logger::write("\n", 1); |
michael@0 | 484 | } |
michael@0 | 485 | |
michael@0 | 486 | bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; |
michael@0 | 487 | if (callback_) |
michael@0 | 488 | success = callback_(minidump_descriptor_, callback_context_, success); |
michael@0 | 489 | return success; |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 493 | void ExceptionHandler::SendContinueSignalToChild() { |
michael@0 | 494 | static const char okToContinueMessage = 'a'; |
michael@0 | 495 | int r; |
michael@0 | 496 | r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char))); |
michael@0 | 497 | if(r == -1) { |
michael@0 | 498 | static const char msg[] = "ExceptionHandler::SendContinueSignalToChild \ |
michael@0 | 499 | sys_write failed:"; |
michael@0 | 500 | logger::write(msg, sizeof(msg) - 1); |
michael@0 | 501 | logger::write(strerror(errno), strlen(strerror(errno))); |
michael@0 | 502 | logger::write("\n", 1); |
michael@0 | 503 | } |
michael@0 | 504 | } |
michael@0 | 505 | |
michael@0 | 506 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 507 | // Runs on the cloned process. |
michael@0 | 508 | void ExceptionHandler::WaitForContinueSignal() { |
michael@0 | 509 | int r; |
michael@0 | 510 | char receivedMessage; |
michael@0 | 511 | r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char))); |
michael@0 | 512 | if(r == -1) { |
michael@0 | 513 | static const char msg[] = "ExceptionHandler::WaitForContinueSignal \ |
michael@0 | 514 | sys_read failed:"; |
michael@0 | 515 | logger::write(msg, sizeof(msg) - 1); |
michael@0 | 516 | logger::write(strerror(errno), strlen(strerror(errno))); |
michael@0 | 517 | logger::write("\n", 1); |
michael@0 | 518 | } |
michael@0 | 519 | } |
michael@0 | 520 | |
michael@0 | 521 | // This function runs in a compromised context: see the top of the file. |
michael@0 | 522 | // Runs on the cloned process. |
michael@0 | 523 | bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, |
michael@0 | 524 | size_t context_size) { |
michael@0 | 525 | if (minidump_descriptor_.IsFD()) { |
michael@0 | 526 | return google_breakpad::WriteMinidump(minidump_descriptor_.fd(), |
michael@0 | 527 | minidump_descriptor_.size_limit(), |
michael@0 | 528 | crashing_process, |
michael@0 | 529 | context, |
michael@0 | 530 | context_size, |
michael@0 | 531 | mapping_list_, |
michael@0 | 532 | app_memory_list_); |
michael@0 | 533 | } |
michael@0 | 534 | return google_breakpad::WriteMinidump(minidump_descriptor_.path(), |
michael@0 | 535 | minidump_descriptor_.size_limit(), |
michael@0 | 536 | crashing_process, |
michael@0 | 537 | context, |
michael@0 | 538 | context_size, |
michael@0 | 539 | mapping_list_, |
michael@0 | 540 | app_memory_list_); |
michael@0 | 541 | } |
michael@0 | 542 | |
michael@0 | 543 | // static |
michael@0 | 544 | bool ExceptionHandler::WriteMinidump(const string& dump_path, |
michael@0 | 545 | MinidumpCallback callback, |
michael@0 | 546 | void* callback_context) { |
michael@0 | 547 | MinidumpDescriptor descriptor(dump_path); |
michael@0 | 548 | ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1); |
michael@0 | 549 | return eh.WriteMinidump(); |
michael@0 | 550 | } |
michael@0 | 551 | |
michael@0 | 552 | bool ExceptionHandler::WriteMinidump() { |
michael@0 | 553 | if (!IsOutOfProcess() && !minidump_descriptor_.IsFD()) { |
michael@0 | 554 | // Update the path of the minidump so that this can be called multiple times |
michael@0 | 555 | // and new files are created for each minidump. This is done before the |
michael@0 | 556 | // generation happens, as clients may want to access the MinidumpDescriptor |
michael@0 | 557 | // after this call to find the exact path to the minidump file. |
michael@0 | 558 | minidump_descriptor_.UpdatePath(); |
michael@0 | 559 | } else if (minidump_descriptor_.IsFD()) { |
michael@0 | 560 | // Reposition the FD to its beginning and resize it to get rid of the |
michael@0 | 561 | // previous minidump info. |
michael@0 | 562 | lseek(minidump_descriptor_.fd(), 0, SEEK_SET); |
michael@0 | 563 | static_cast<void>(ftruncate(minidump_descriptor_.fd(), 0)); |
michael@0 | 564 | } |
michael@0 | 565 | |
michael@0 | 566 | // Allow this process to be dumped. |
michael@0 | 567 | sys_prctl(PR_SET_DUMPABLE, 1); |
michael@0 | 568 | |
michael@0 | 569 | CrashContext context; |
michael@0 | 570 | int getcontext_result = getcontext(&context.context); |
michael@0 | 571 | if (getcontext_result) |
michael@0 | 572 | return false; |
michael@0 | 573 | #if !defined(__ARM_EABI__) |
michael@0 | 574 | // FPU state is not part of ARM EABI ucontext_t. |
michael@0 | 575 | memcpy(&context.float_state, context.context.uc_mcontext.fpregs, |
michael@0 | 576 | sizeof(context.float_state)); |
michael@0 | 577 | #endif |
michael@0 | 578 | context.tid = sys_gettid(); |
michael@0 | 579 | |
michael@0 | 580 | // Add an exception stream to the minidump for better reporting. |
michael@0 | 581 | memset(&context.siginfo, 0, sizeof(context.siginfo)); |
michael@0 | 582 | context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED; |
michael@0 | 583 | #if defined(__i386__) |
michael@0 | 584 | context.siginfo.si_addr = |
michael@0 | 585 | reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_EIP]); |
michael@0 | 586 | #elif defined(__x86_64__) |
michael@0 | 587 | context.siginfo.si_addr = |
michael@0 | 588 | reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]); |
michael@0 | 589 | #elif defined(__arm__) |
michael@0 | 590 | context.siginfo.si_addr = |
michael@0 | 591 | reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc); |
michael@0 | 592 | #else |
michael@0 | 593 | #error "This code has not been ported to your platform yet." |
michael@0 | 594 | #endif |
michael@0 | 595 | |
michael@0 | 596 | return GenerateDump(&context); |
michael@0 | 597 | } |
michael@0 | 598 | |
michael@0 | 599 | void ExceptionHandler::AddMappingInfo(const string& name, |
michael@0 | 600 | const uint8_t identifier[sizeof(MDGUID)], |
michael@0 | 601 | uintptr_t start_address, |
michael@0 | 602 | size_t mapping_size, |
michael@0 | 603 | size_t file_offset) { |
michael@0 | 604 | MappingInfo info; |
michael@0 | 605 | info.start_addr = start_address; |
michael@0 | 606 | info.size = mapping_size; |
michael@0 | 607 | info.offset = file_offset; |
michael@0 | 608 | strncpy(info.name, name.c_str(), sizeof(info.name) - 1); |
michael@0 | 609 | info.name[sizeof(info.name) - 1] = '\0'; |
michael@0 | 610 | |
michael@0 | 611 | MappingEntry mapping; |
michael@0 | 612 | mapping.first = info; |
michael@0 | 613 | memcpy(mapping.second, identifier, sizeof(MDGUID)); |
michael@0 | 614 | mapping_list_.push_back(mapping); |
michael@0 | 615 | } |
michael@0 | 616 | |
michael@0 | 617 | void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { |
michael@0 | 618 | AppMemoryList::iterator iter = |
michael@0 | 619 | std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); |
michael@0 | 620 | if (iter != app_memory_list_.end()) { |
michael@0 | 621 | // Don't allow registering the same pointer twice. |
michael@0 | 622 | return; |
michael@0 | 623 | } |
michael@0 | 624 | |
michael@0 | 625 | AppMemory app_memory; |
michael@0 | 626 | app_memory.ptr = ptr; |
michael@0 | 627 | app_memory.length = length; |
michael@0 | 628 | app_memory_list_.push_back(app_memory); |
michael@0 | 629 | } |
michael@0 | 630 | |
michael@0 | 631 | void ExceptionHandler::UnregisterAppMemory(void* ptr) { |
michael@0 | 632 | AppMemoryList::iterator iter = |
michael@0 | 633 | std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); |
michael@0 | 634 | if (iter != app_memory_list_.end()) { |
michael@0 | 635 | app_memory_list_.erase(iter); |
michael@0 | 636 | } |
michael@0 | 637 | } |
michael@0 | 638 | |
michael@0 | 639 | // static |
michael@0 | 640 | bool ExceptionHandler::WriteMinidumpForChild(pid_t child, |
michael@0 | 641 | pid_t child_blamed_thread, |
michael@0 | 642 | const string& dump_path, |
michael@0 | 643 | MinidumpCallback callback, |
michael@0 | 644 | void* callback_context) { |
michael@0 | 645 | // This function is not run in a compromised context. |
michael@0 | 646 | MinidumpDescriptor descriptor(dump_path); |
michael@0 | 647 | descriptor.UpdatePath(); |
michael@0 | 648 | if (!google_breakpad::WriteMinidump(descriptor.path(), |
michael@0 | 649 | child, |
michael@0 | 650 | child_blamed_thread)) |
michael@0 | 651 | return false; |
michael@0 | 652 | |
michael@0 | 653 | return callback ? callback(descriptor, callback_context, true) : true; |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | } // namespace google_breakpad |