static void wait_for_user_action(const debugger_request_t& request) { // Explain how to attach the debugger. ALOGI("***********************************************************\n" "* Process %d has been suspended while crashing.\n" "* To attach gdbserver and start gdb, run this on the host:\n" "*\n" "* gdbclient %d\n" "*\n" "* Wait for gdb to start, then press the VOLUME DOWN key\n" "* to let the process continue crashing.\n" "***********************************************************", request.pid, request.tid); // Wait for VOLUME DOWN. if (init_getevent() == 0) { while (true) { input_event e; if (get_event(&e, -1) == 0) { if (e.type == EV_KEY && e.code == KEY_VOLUMEDOWN && e.value == 0) { break; } } } uninit_getevent(); } ALOGI("debuggerd resuming process %d", request.pid); }
static void wait_for_user_action(pid_t pid) { // First log a helpful message LOG( "********************************************************\n" "* Process %d has been suspended while crashing. To\n" "* attach gdbserver for a gdb connection on port 5039\n" "* and start gdbclient:\n" "*\n" "* gdbclient app_process :5039 %d\n" "*\n" "* Wait for gdb to start, then press HOME or VOLUME DOWN key\n" "* to let the process continue crashing.\n" "********************************************************\n", pid, pid); // wait for HOME or VOLUME DOWN key if (init_getevent() == 0) { int ms = 1200 / 10; int dit = 1; int dah = 3*dit; int _ = -dit; int ___ = 3*_; int _______ = 7*_; const int codes[] = { dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ }; size_t s = 0; input_event e; bool done = false; init_debug_led(); enable_debug_led(); do { int timeout = abs(codes[s]) * ms; int res = get_event(&e, timeout); if (res == 0) { if (e.type == EV_KEY && (e.code == KEY_HOME || e.code == KEY_VOLUMEDOWN) && e.value == 0) { done = true; } } else if (res == 1) { if (++s >= sizeof(codes)/sizeof(*codes)) s = 0; if (codes[s] > 0) { enable_debug_led(); } else { disable_debug_led(); } } } while (!done); uninit_getevent(); } // don't forget to turn debug led off disable_debug_led(); LOG("debuggerd resuming process %d", pid); }
static void wait_for_user_action(unsigned tid, struct ucred* cr) { (void)tid; /* First log a helpful message */ LOG( "********************************************************\n" "* Process %d has been suspended while crashing. To\n" "* attach gdbserver for a gdb connection on port 5039:\n" "*\n" "* adb shell gdbserver :5039 --attach %d &\n" "*\n" "* Press HOME key to let the process continue crashing.\n" "********************************************************\n", cr->pid, cr->pid); /* wait for HOME key (TODO: something useful for devices w/o HOME key) */ if (init_getevent() == 0) { int ms = 1200 / 10; int dit = 1; int dah = 3*dit; int _ = -dit; int ___ = 3*_; int _______ = 7*_; const signed char codes[] = { dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ }; size_t s = 0; struct input_event e; int home = 0; init_debug_led(); enable_debug_led(); do { int timeout = abs((int)(codes[s])) * ms; int res = get_event(&e, timeout); if (res == 0) { if (e.type==EV_KEY && e.code==KEY_HOME && e.value==0) home = 1; } else if (res == 1) { if (++s >= sizeof(codes)/sizeof(*codes)) s = 0; if (codes[s] > 0) { enable_debug_led(); } else { disable_debug_led(); } } } while (!home); uninit_getevent(); } /* don't forget to turn debug led off */ disable_debug_led(); /* close filedescriptor */ LOG("debuggerd resuming process %d", cr->pid); }
static void wait_for_user_action(unsigned tid, struct ucred* cr) { (void)tid; /* First log a helpful message */ LOG( "********************************************************\n" "* process %d crashed. debuggerd waiting for gdbserver \n" "* \n" "* adb shell gdbserver :port --attach %d & \n" "* \n" "* and press the HOME key. \n" "********************************************************\n", cr->pid, cr->pid); /* wait for HOME key */ if (init_getevent() == 0) { int ms = 1200 / 10; int dit = 1; int dah = 3*dit; int _ = -dit; int ___ = 3*_; int _______ = 7*_; const signed char codes[] = { dit,_,dit,_,dit,___,dah,_,dah,_,dah,___,dit,_,dit,_,dit,_______ }; size_t s = 0; struct input_event e; int home = 0; init_debug_led(); enable_debug_led(); do { int timeout = abs((int)(codes[s])) * ms; int res = get_event(&e, timeout); if (res == 0) { if (e.type==EV_KEY && e.code==KEY_HOME && e.value==0) home = 1; } else if (res == 1) { if (++s >= sizeof(codes)/sizeof(*codes)) s = 0; if (codes[s] > 0) { enable_debug_led(); } else { disable_debug_led(); } } } while (!home); uninit_getevent(); } /* don't forget to turn debug led off */ disable_debug_led(); /* close filedescriptor */ LOG("debuggerd resuming process %d", cr->pid); }
static void wait_for_user_action(const debugger_request_t &request) { // Find out the name of the process that crashed. char path[64]; snprintf(path, sizeof(path), "/proc/%d/exe", request.pid); char exe[PATH_MAX]; int count; if ((count = readlink(path, exe, sizeof(exe) - 1)) == -1) { ALOGE("readlink('%s') failed: %s", path, strerror(errno)); strlcpy(exe, "unknown", sizeof(exe)); } else { exe[count] = '\0'; } // Explain how to attach the debugger. ALOGI("********************************************************\n" "* Process %d has been suspended while crashing.\n" "* To attach gdbserver for a gdb connection on port 5039\n" "* and start gdbclient:\n" "*\n" "* gdbclient %s :5039 %d\n" "*\n" "* Wait for gdb to start, then press the VOLUME DOWN key\n" "* to let the process continue crashing.\n" "********************************************************\n", request.pid, exe, request.tid); // Wait for VOLUME DOWN. if (init_getevent() == 0) { while (true) { input_event e; if (get_event(&e, -1) == 0) { if (e.type == EV_KEY && e.code == KEY_VOLUMEDOWN && e.value == 0) { break; } } } uninit_getevent(); } ALOGI("debuggerd resuming process %d", request.pid); }
static void worker_process(int fd, debugger_request_t& request) { // Open the tombstone file if we need it. std::string tombstone_path; int tombstone_fd = -1; switch (request.action) { case DEBUGGER_ACTION_DUMP_TOMBSTONE: case DEBUGGER_ACTION_CRASH: tombstone_fd = open_tombstone(&tombstone_path); if (tombstone_fd == -1) { ALOGE("debuggerd: failed to open tombstone file: %s\n", strerror(errno)); exit(1); } break; case DEBUGGER_ACTION_DUMP_BACKTRACE: break; default: ALOGE("debuggerd: unexpected request action: %d", request.action); exit(1); } // At this point, the thread that made the request is blocked in // a read() call. If the thread has crashed, then this gives us // time to PTRACE_ATTACH to it before it has a chance to really fault. // // The PTRACE_ATTACH sends a SIGSTOP to the target process, but it // won't necessarily have stopped by the time ptrace() returns. (We // currently assume it does.) We write to the file descriptor to // ensure that it can run as soon as we call PTRACE_CONT below. // See details in bionic/libc/linker/debugger.c, in function // debugger_signal_handler(). // Attach to the target process. if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno)); exit(1); } // Don't attach to the sibling threads if we want to attach gdb. // Supposedly, it makes the process less reliable. bool attach_gdb = should_attach_gdb(request); if (attach_gdb) { // Open all of the input devices we need to listen for VOLUMEDOWN before dropping privileges. if (init_getevent() != 0) { ALOGE("debuggerd: failed to initialize input device, not waiting for gdb"); attach_gdb = false; } } std::set<pid_t> siblings; if (!attach_gdb) { ptrace_siblings(request.pid, request.tid, siblings); } // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); bool succeeded = false; // Now that we've done everything that requires privileges, we can drop them. if (!drop_privileges()) { ALOGE("debuggerd: failed to drop privileges, exiting"); _exit(1); } int crash_signal = SIGKILL; succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), siblings, &crash_signal); if (succeeded) { if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { if (!tombstone_path.empty()) { write(fd, tombstone_path.c_str(), tombstone_path.length()); } } } if (attach_gdb) { // Tell the signal process to send SIGSTOP to the target. if (!send_signal(request.pid, 0, SIGSTOP)) { ALOGE("debuggerd: failed to stop process for gdb attach: %s", strerror(errno)); attach_gdb = false; } } if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno)); } for (pid_t sibling : siblings) { ptrace(PTRACE_DETACH, sibling, 0, 0); } // Send the signal back to the process if it crashed and we're not waiting for gdb. if (!attach_gdb && request.action == DEBUGGER_ACTION_CRASH) { if (!send_signal(request.pid, request.tid, crash_signal)) { ALOGE("debuggerd: failed to kill process %d: %s", request.pid, strerror(errno)); } } // Wait for gdb, if requested. if (attach_gdb && succeeded) { wait_for_user_action(request); // Tell the signal process to send SIGCONT to the target. if (!send_signal(request.pid, 0, SIGCONT)) { ALOGE("debuggerd: failed to resume process %d: %s", request.pid, strerror(errno)); } uninit_getevent(); } exit(!succeeded); }
static void handle_request(int fd) { ALOGV("handle_request(%d)\n", fd); ScopedFd closer(fd); debugger_request_t request; memset(&request, 0, sizeof(request)); int status = read_request(fd, &request); if (status != 0) { return; } ALOGV("BOOM: pid=%d uid=%d gid=%d tid=%d\n", request.pid, request.uid, request.gid, request.tid); #if defined(__LP64__) // On 64 bit systems, requests to dump 32 bit and 64 bit tids come // to the 64 bit debuggerd. If the process is a 32 bit executable, // redirect the request to the 32 bit debuggerd. if (is32bit(request.tid)) { // Only dump backtrace and dump tombstone requests can be redirected. if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE || request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { redirect_to_32(fd, &request); } else { ALOGE("debuggerd: Not allowed to redirect action %d to 32 bit debuggerd\n", request.action); } return; } #endif // Fork a child to handle the rest of the request. pid_t fork_pid = fork(); if (fork_pid == -1) { ALOGE("debuggerd: failed to fork: %s\n", strerror(errno)); return; } else if (fork_pid != 0) { waitpid(fork_pid, nullptr, 0); return; } // Open the tombstone file if we need it. std::string tombstone_path; int tombstone_fd = -1; switch (request.action) { case DEBUGGER_ACTION_DUMP_TOMBSTONE: case DEBUGGER_ACTION_CRASH: tombstone_fd = open_tombstone(&tombstone_path); if (tombstone_fd == -1) { ALOGE("debuggerd: failed to open tombstone file: %s\n", strerror(errno)); exit(1); } break; case DEBUGGER_ACTION_DUMP_BACKTRACE: break; default: ALOGE("debuggerd: unexpected request action: %d", request.action); exit(1); } // At this point, the thread that made the request is blocked in // a read() call. If the thread has crashed, then this gives us // time to PTRACE_ATTACH to it before it has a chance to really fault. // // The PTRACE_ATTACH sends a SIGSTOP to the target process, but it // won't necessarily have stopped by the time ptrace() returns. (We // currently assume it does.) We write to the file descriptor to // ensure that it can run as soon as we call PTRACE_CONT below. // See details in bionic/libc/linker/debugger.c, in function // debugger_signal_handler(). // Attach to the target process. if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno)); exit(1); } // Don't attach to the sibling threads if we want to attach gdb. // Supposedly, it makes the process less reliable. bool attach_gdb = should_attach_gdb(&request); int signal_in_fd = -1; int signal_out_fd = -1; pid_t signal_pid = 0; if (attach_gdb) { // Open all of the input devices we need to listen for VOLUMEDOWN before dropping privileges. if (init_getevent() != 0) { ALOGE("debuggerd: failed to initialize input device, not waiting for gdb"); attach_gdb = false; } // Fork a process that stays root, and listens on a pipe to pause and resume the target. if (!fork_signal_sender(&signal_in_fd, &signal_out_fd, &signal_pid, request.pid)) { attach_gdb = false; } } auto notify_signal_sender = [=]() { char buf[1]; if (TEMP_FAILURE_RETRY(write(signal_in_fd, "", 1)) != 1) { ALOGE("debuggerd: failed to notify signal process: %s", strerror(errno)); } else if (TEMP_FAILURE_RETRY(read(signal_out_fd, buf, 1)) != 1) { ALOGE("debuggerd: failed to read response from signal process: %s", strerror(errno)); } }; std::set<pid_t> siblings; if (!attach_gdb) { ptrace_siblings(request.pid, request.tid, siblings); } // Generate the backtrace map before dropping privileges. std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid)); bool succeeded = false; // Now that we've done everything that requires privileges, we can drop them. if (drop_privileges()) { succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), siblings); if (succeeded) { if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { if (!tombstone_path.empty()) { write(fd, tombstone_path.c_str(), tombstone_path.length()); } } } if (attach_gdb) { // Tell the signal process to send SIGSTOP to the target. notify_signal_sender(); } } if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) { ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno)); } for (pid_t sibling : siblings) { ptrace(PTRACE_DETACH, sibling, 0, 0); } // Wait for gdb, if requested. if (attach_gdb && succeeded) { wait_for_user_action(request); // Tell the signal process to send SIGCONT to the target. notify_signal_sender(); uninit_getevent(); waitpid(signal_pid, nullptr, 0); } exit(!succeeded); }