From e3a20c7d68f0e08c1106eccc3d00205641213dc3 Mon Sep 17 00:00:00 2001 From: Michael Ensslin Date: Sat, 29 Aug 2020 14:29:02 +0200 Subject: [PATCH 1/4] Add 'press g to attach gdb' feature --- Action.c | 20 ++++++++++++++++++++ htop.1.in | 3 +++ 2 files changed, 23 insertions(+) diff --git a/Action.c b/Action.c index 79a4225dc..34b01ea20 100644 --- a/Action.c +++ b/Action.c @@ -378,6 +378,24 @@ static Htop_Reaction actionStrace(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } +static Htop_Reaction actionGdb(State* st) { + Process* p = (Process*) Panel_getSelected(st->panel); + if (!p) return HTOP_OK; + + char buf[32]; + xSnprintf(buf, sizeof(buf), "gdb -p %d", (int) p->pid); + + endwin(); + int result = system(buf); + refresh(); + + // we don't really care about the invocation result + (void) result; + + CRT_enableDelay(); + return HTOP_REFRESH | HTOP_REDRAW_BAR; +} + static Htop_Reaction actionTag(State* st) { Process* p = (Process*) Panel_getSelected(st->panel); if (!p) return HTOP_OK; @@ -423,6 +441,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 = " g: ", .info = "debug with gdb" }, { .key = " ", .info = "" }, { .key = " F2 C S: ", .info = "setup" }, { .key = " F1 h: ", .info = "show this help screen" }, @@ -576,6 +595,7 @@ void Action_setBindings(Htop_Action* keys) { keys[KEY_F(2)] = actionSetup; keys['l'] = actionLsof; keys['s'] = actionStrace; + keys['g'] = actionGdb; keys[' '] = actionTag; keys['\014'] = actionRedraw; // Ctrl+L keys[KEY_F(1)] = actionHelp; diff --git a/htop.1.in b/htop.1.in index cbd5453c1..0241c6f27 100644 --- a/htop.1.in +++ b/htop.1.in @@ -115,6 +115,9 @@ 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 g +Debug process: if gdb(1) is installed, pressing this key will attach it +to the currently selected process, allowing you to debug it. .TP .B F1, h, ? Go to the help screen From 581190df63093344dca9f2f58b4fd5b233018b46 Mon Sep 17 00:00:00 2001 From: Michael Ensslin Date: Sun, 30 Aug 2020 19:30:34 +0200 Subject: [PATCH 2/4] Change 'attach debugger' hotkey from 'g' to 'D' make debugger tool configurable via htoprc --- Action.c | 9 +++++---- Settings.c | 8 +++++++- Settings.h | 1 + TESTPLAN | 4 +++- htop.1.in | 7 ++++--- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Action.c b/Action.c index 34b01ea20..ff93588d6 100644 --- a/Action.c +++ b/Action.c @@ -378,12 +378,13 @@ static Htop_Reaction actionStrace(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } -static Htop_Reaction actionGdb(State* st) { +static Htop_Reaction actionDebugger(State* st) { + Settings* settings = st->settings; Process* p = (Process*) Panel_getSelected(st->panel); if (!p) return HTOP_OK; char buf[32]; - xSnprintf(buf, sizeof(buf), "gdb -p %d", (int) p->pid); + xSnprintf(buf, sizeof(buf), "%s -p %d", settings->debuggerTool, (int) p->pid); endwin(); int result = system(buf); @@ -441,7 +442,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 = " g: ", .info = "debug with gdb" }, + { .key = " D: ", .info = "attach debugger" }, { .key = " ", .info = "" }, { .key = " F2 C S: ", .info = "setup" }, { .key = " F1 h: ", .info = "show this help screen" }, @@ -595,7 +596,7 @@ void Action_setBindings(Htop_Action* keys) { keys[KEY_F(2)] = actionSetup; keys['l'] = actionLsof; keys['s'] = actionStrace; - keys['g'] = actionGdb; + 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 0241c6f27..8d83d7c51 100644 --- a/htop.1.in +++ b/htop.1.in @@ -115,9 +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 g -Debug process: if gdb(1) is installed, pressing this key will attach it -to the currently selected process, allowing you to debug it. +.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 From 787cd83c82e2236ebbdc7f5554eee38b9bcaa8a1 Mon Sep 17 00:00:00 2001 From: Michael Ensslin Date: Fri, 18 Sep 2020 23:16:59 +0200 Subject: [PATCH 3/4] Include HTOP_UPDATE_PANELHDR --- Action.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Action.c b/Action.c index ff93588d6..b85491bfd 100644 --- a/Action.c +++ b/Action.c @@ -394,7 +394,7 @@ static Htop_Reaction actionDebugger(State* st) { (void) result; CRT_enableDelay(); - return HTOP_REFRESH | HTOP_REDRAW_BAR; + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; } static Htop_Reaction actionTag(State* st) { From 5c7d25b46dbd92f380158794bb6080b2094eb780 Mon Sep 17 00:00:00 2001 From: Michael Ensslin Date: Sat, 19 Sep 2020 02:51:22 +0200 Subject: [PATCH 4/4] Launch the debugger with fork/execvp We also ensure that the debugger gets its own foreground process group, so that all its signals are handled properly. --- Action.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/Action.c b/Action.c index b85491bfd..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,21 +383,119 @@ 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; - char buf[32]; - xSnprintf(buf, sizeof(buf), "%s -p %d", settings->debuggerTool, (int) p->pid); + // 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(); + } + } - endwin(); - int result = system(buf); refresh(); - - // we don't really care about the invocation result - (void) result; - CRT_enableDelay(); return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; }