diff --git a/src/kcrash.cpp b/src/kcrash.cpp --- a/src/kcrash.cpp +++ b/src/kcrash.cpp @@ -40,6 +40,7 @@ #include #endif #ifdef Q_OS_LINUX +#include #include #endif @@ -618,6 +619,11 @@ static int read_socket(int sock, char *buffer, int len); static int openSocket(); +#ifdef Q_OS_LINUX +static int openDrKonqiSocket(const QByteArray &socketpath); +static int pollDrKonqiSocket(pid_t pid, int sockfd); +#endif + void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit) { bool startDirectly = true; @@ -646,24 +652,49 @@ if (pid > 0 && waitAndExit) { // Seems we made it.... - alarm(0); //stop the pending alarm that was set at the top of the defaultCrashHandler + alarm(0); // Stop the pending alarm that was set at the top of the defaultCrashHandler + bool running = true; // Wait forever until the started process exits. This code path is executed - // when launching drkonqi. Note that drkonqi will stop this process in the meantime. - if (directly) { - //if the process was started directly, use waitpid(), as it's a child... - while (waitpid(-1, nullptr, 0) != pid) {} - } else { + // when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime + // and only send SIGCONT when it is about to attach a debugger. #ifdef Q_OS_LINUX - // Declare the process that will be debugging the crashed KDE app (#245529) + // Declare the process that will be debugging the crashed KDE app (#245529) + // For now that will be DrKonqi, which may ask to transfer the ptrace scope to + // a debugger using a socket. #ifndef PR_SET_PTRACER # define PR_SET_PTRACER 0x59616d61 #endif - prctl(PR_SET_PTRACER, pid, 0, 0, 0); + prctl(PR_SET_PTRACER, pid, 0, 0, 0); + + // Create socket path to transfer ptrace scope and open connection + const QByteArray socketpath = QFile::encodeName( + QStringLiteral("%1/kcrash_%2").arg(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)) + .arg(getpid())); + int sockfd = openDrKonqiSocket(socketpath); + + if (sockfd >= 0) { + // Wait while DrKonqi is running and the socket connection exists + if (directly) { + // If the process was started directly, use waitpid(), as it's a child... + while ((running = waitpid(pid, nullptr, WNOHANG) != pid) && pollDrKonqiSocket(pid, sockfd) >= 0) {} + } else { + // ... else poll its status using kill() + while ((running = kill(pid, 0) >= 0) && pollDrKonqiSocket(pid, sockfd) >= 0) {} + } + close(sockfd); + unlink(socketpath.constData()); + } #endif - //...else poll its status using kill() - while (kill(pid, 0) >= 0) { - sleep(1); + if (running) { + if (directly) { + // If the process was started directly, use waitpid(), as it's a child... + while (waitpid(pid, nullptr, 0) != pid) {} + } else { + // ... else poll its status using kill() + while (kill(pid, 0) >= 0) { + sleep(1); + } } } if (!s_coreConfig->isProcess()) { @@ -834,4 +865,90 @@ return s; } +#ifdef Q_OS_LINUX + +static int openDrKonqiSocket(const QByteArray &socketpath) +{ + int sockfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (sockfd < 0) { + perror("Warning: socket() for communication with DrKonqi failed"); + return -1; + } + + struct sockaddr_un drkonqi_server; + drkonqi_server.sun_family = AF_UNIX; + + if (socketpath.size() >= static_cast(sizeof(drkonqi_server.sun_path))) { + fprintf(stderr, "Warning: socket path is too long\n"); + close(sockfd); + return -1; + } + strcpy(drkonqi_server.sun_path, socketpath.constData()); + + unlink(drkonqi_server.sun_path); // remove potential stale socket + if (bind(sockfd, (struct sockaddr *)&drkonqi_server, sizeof(drkonqi_server)) < 0) { + perror("Warning: bind() for communication with DrKonqi failed"); + close(sockfd); + unlink(drkonqi_server.sun_path); + return -1; + } + + listen(sockfd, 1); + + return sockfd; +} + +static int pollDrKonqiSocket(pid_t pid, int sockfd) +{ + struct pollfd fd; + fd.fd = sockfd; + fd.events = POLLIN; + int r; + do { + r = poll(&fd, 1, 1000); // wait for 1 second for a request by DrKonqi + } while (r == -1 && errno == EINTR); + // only continue if POLLIN event returned + if (r == 0) // timeout + return 0; + else if (r == -1 || !(fd.revents & POLLIN)) // some error + return -1; + + static struct sockaddr_un drkonqi_client; + static socklen_t cllength = sizeof(drkonqi_client); + int clsockfd; + do { + clsockfd = accept(sockfd, (struct sockaddr *)&drkonqi_client, &cllength); + } while (clsockfd == -1 && errno == EINTR); + if (clsockfd < 0) + return -1; + + // check whether the message is coming from DrKonqi + static struct ucred ucred; + static socklen_t credlen = sizeof(struct ucred); + if (getsockopt(clsockfd, SOL_SOCKET, SO_PEERCRED, &ucred, &credlen) < 0) + return -1; + + if (ucred.pid != pid) { + fprintf(stderr, "Warning: peer pid does not match DrKonqi pid\n"); + return -1; + } + + // read PID to change ptrace scope + static const int msize = 21; // most digits in a 64bit int (+sign +'\0') + char msg[msize]; + if (read_socket(clsockfd, msg, msize) == 0) { + int dpid = atoi(msg); + prctl(PR_SET_PTRACER, dpid, 0, 0, 0); + // confirm change to DrKonqi + if (write_socket(clsockfd, msg, msize) == 0) { + fprintf(stderr, "KCrash: ptrace access transferred to %s\n", msg); + } + } + close(clsockfd); + + return 1; +} + +#endif + #endif // Q_OS_UNIX