diff --git a/Action.c b/Action.c index 79a4225dc..2c6d85078 100644 --- a/Action.c +++ b/Action.c @@ -25,10 +25,15 @@ in the source distribution for its full text. #include #include #include +#include #include #include #include #include +#include +#include +#include +#include Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) { Panel* panel = st->panel; @@ -378,6 +383,123 @@ static Htop_Reaction actionStrace(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } +/** + * Creates a subprocess with the given argv. + * + * argv must be a null-terminated array of strings, + * and will be passed to execvp. + * + * The subprocess is launched as a child process, + * in its own foreground process group; + * this decouples signals like SIGINT from htop. + */ +bool launchSubProcess(char *const argv[]) { + endwin(); + + // backup the current terminal settings + struct termios old_attrs; + if (tcgetattr(STDIN_FILENO, &old_attrs) < 0) + { + perror("tcgetattr()"); + return false; + } + + // backup the current foreground process group + pid_t old_pgrp = tcgetpgrp(STDIN_FILENO); + if (old_pgrp < 0) { + perror("tcgetpgrp()"); + return false; + } + + pid_t child_pid = fork(); + if (child_pid == -1) { + perror("fork()"); + return false; + } + + if (child_pid == 0) { + // this is the child process. launch the subprocess + printf("htop: launching"); + for (size_t i = 0; argv[i] != NULL; i++) { + printf(" %s", argv[i]); + } + putchar('\n'); + execvp(argv[0], argv); + // we should never reach this line. + // most likely cause of error: argv[0] doesn't exist + perror("excecvp()"); + exit(1); + } + + // setup a process group for the child + setpgid(child_pid, child_pid); + tcsetpgrp(STDIN_FILENO, child_pid); + // if the child prints to stdout between setpgid and tcsetpgrp, + // it is stopped; we need to send SIGCONT to prevent this. + kill(child_pid, SIGCONT); + + // wait for the child to terminate. + int child_status; + while (waitpid(child_pid, &child_status, 0) == child_pid) {} + + // restore the foreground process group + // we must ignore SIGTTOU while we do this + void (*old_handler)(int) = signal(SIGTTOU, SIG_IGN); + tcsetpgrp(STDIN_FILENO, old_pgrp); + if (old_handler != SIG_ERR) { + signal(SIGTTOU, old_handler); + } + + // restore the terminal settings + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &old_attrs) < 0) { + perror("tcsetattr()"); + return false; + } + + // check the child exit status + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) == 0) { + // all good! + return true; + } + printf("child exited with %d\n", WEXITSTATUS(child_status)); + return false; + } else if (WIFSIGNALED(child_status)) { + printf("child killed (signal %d)\n", WTERMSIG(child_status)); + return false; + } else { + printf("unexpected wait() status: 0x%x\n", child_status); + return false; + } +} + +static Htop_Reaction actionDebugger(State* st) { + Settings* settings = st->settings; + Process* p = (Process*) Panel_getSelected(st->panel); + if (!p) return HTOP_OK; + + // prepare the argv for the debugger subprocess + char *arg0 = settings->debuggerTool; + char arg1[] = "-p"; + char arg2[64]; + xSnprintf(arg2, sizeof(arg2), "%d", (int) p->pid); + char *const argv[] = { arg0, arg1, arg2, NULL }; + + if (!launchSubProcess(argv)) { + // something went wrong + // pause to allow the user to read the error message(s) + printf("press ENTER to continue"); + int c = 0; + while (c != EOF && c != '\n') { + c = getchar(); + } + } + + refresh(); + CRT_enableDelay(); + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; +} + static Htop_Reaction actionTag(State* st) { Process* p = (Process*) Panel_getSelected(st->panel); if (!p) return HTOP_OK; @@ -423,6 +545,7 @@ static const struct { const char* key; const char* info; } helpRight[] = { { .key = " i: ", .info = "set IO priority" }, { .key = " l: ", .info = "list open files with lsof" }, { .key = " s: ", .info = "trace syscalls with strace" }, + { .key = " D: ", .info = "attach debugger" }, { .key = " ", .info = "" }, { .key = " F2 C S: ", .info = "setup" }, { .key = " F1 h: ", .info = "show this help screen" }, @@ -576,6 +699,7 @@ void Action_setBindings(Htop_Action* keys) { keys[KEY_F(2)] = actionSetup; keys['l'] = actionLsof; keys['s'] = actionStrace; + keys['D'] = actionDebugger; keys[' '] = actionTag; keys['\014'] = actionRedraw; // Ctrl+L keys[KEY_F(1)] = actionHelp; diff --git a/Settings.c b/Settings.c index 0aac47968..e6eaf2473 100644 --- a/Settings.c +++ b/Settings.c @@ -24,6 +24,7 @@ void Settings_delete(Settings* this) { String_freeArray(this->columns[i].names); free(this->columns[i].modes); } + free(this->debuggerTool); free(this); } @@ -181,7 +182,10 @@ static bool Settings_read(Settings* this, const char* fileName) { } else if (String_eq(option[0], "color_scheme")) { this->colorScheme = atoi(option[1]); if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) this->colorScheme = 0; - } else if (String_eq(option[0], "enable_mouse")) { + } else if (String_eq(option[0], "debugger_tool")) { + this->debuggerTool = String_trim(option[1]); + if (strlen(this->debuggerTool) == 0) this->debuggerTool = xStrdup("gdb"); + } else if (String_eq(option[0], "enable_mouse")) { this->enableMouse = atoi(option[1]); } else if (String_eq(option[0], "left_meters")) { Settings_readMeters(this, option[1], 0); @@ -272,6 +276,7 @@ bool Settings_write(Settings* this) { fprintf(fd, "update_process_names=%d\n", (int) this->updateProcessNames); fprintf(fd, "account_guest_in_cpu_meter=%d\n", (int) this->accountGuestInCPUMeter); fprintf(fd, "color_scheme=%d\n", (int) this->colorScheme); + fprintf(fd, "debugger_tool=%s\n", this->debuggerTool); fprintf(fd, "enable_mouse=%d\n", (int) this->enableMouse); fprintf(fd, "delay=%d\n", (int) this->delay); fprintf(fd, "left_meters="); writeMeters(this, fd, 0); @@ -353,6 +358,7 @@ Settings* Settings_new(int cpuCount) { CRT_restorePrivileges(); } this->colorScheme = 0; + this->debuggerTool = xStrdup("gdb"); this->enableMouse = true; this->changed = false; this->delay = DEFAULT_DELAY; diff --git a/Settings.h b/Settings.h index e1518ecaf..50be2ac22 100644 --- a/Settings.h +++ b/Settings.h @@ -25,6 +25,7 @@ typedef struct Settings_ { ProcessField* fields; int flags; int colorScheme; + char *debuggerTool; int delay; int cpuCount; diff --git a/TESTPLAN b/TESTPLAN index 7669485fb..044cb4a76 100644 --- a/TESTPLAN +++ b/TESTPLAN @@ -73,7 +73,9 @@ Main screen: , , - enter Setup screen. - , - do nothing. + - attach debugger (must be allowed by ptrace_scope) + + - do nothing. - follow process. diff --git a/htop.1.in b/htop.1.in index cbd5453c1..8d83d7c51 100644 --- a/htop.1.in +++ b/htop.1.in @@ -115,6 +115,10 @@ update of system calls issued by the process. .B l Display open files for a process: if lsof(1) is installed, pressing this key will display the list of file descriptors opened by the process. +.B D +Attach debugger to process: if the configured debugger tool (default: gdb(1)) +is installed, pressing this key will launch and attach it to the currently +selected process. .TP .B F1, h, ? Go to the help screen