From 3fbfc9d469406d8399ab9c079ae9ab3405fc936f Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Sun, 23 Jul 2023 11:11:58 +0200 Subject: [PATCH 1/5] Add unwind-ptrace library Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- README.md | 5 +++++ configure.ac | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/README.md b/README.md index f88b2c700..42a2a3c41 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,11 @@ To install on the local system run `make install`. By default `make install` ins enable Linux delay accounting support - dependencies: *libnl-3-dev*(build-time) and *libnl-genl-3-dev*(build-time), at runtime *libnl-3* and *libnl-genl-3* are loaded via `dlopen(3)` if available and requested - default: *check* + * `--enable-backtrace`: + enable backtrace support + - default: *no* + - possible values: + - unwind-ptrace: use unwind-ptrace to get the backtraces ## Runtime dependencies: diff --git a/configure.ac b/configure.ac index 616c06f3b..0ecd5aee1 100644 --- a/configure.ac +++ b/configure.ac @@ -1165,6 +1165,64 @@ char** (*fn)(void* const*, BACKTRACE_RETURN_TYPE) = backtrace_symbols; ) fi +AC_ARG_ENABLE( + [backtrace], + [AS_HELP_STRING( + [--enable-backtrace=], + [enable printing backtraces.] + )], + [], + [enable_backtrace=no] +) +if test "x$enable_backtrace" = xyes; then + case $my_htop_platform in + linux) + enable_backtrace=unwind-ptrace + ;; + *) + AC_MSG_ERROR([backtrace feature not implemented for the '$my_htop_platform' platform]) + ;; + esac +fi + +case "$enable_backtrace" in + no) + ;; + unwind-ptrace) + HTOP_PKG_CHECK_MODULES(LIBUNWIND_PTRACE, libunwind-ptrace, + [ + AM_CFLAGS="$AM_CFLAGS $LIBUNWIND_PTRACE_CFLAGS" + LIBS="$LIBS $LIBUNWIND_PTRACE_LIBS" + ], [ + AC_MSG_ERROR([can not find required library libunwind-ptrace and libunwind-generic]) + ] + ) + AC_DEFINE([HAVE_LIBUNWIND_PTRACE], [1], [Define if libunwind-ptrace is present]) + ;; + *) + AC_MSG_ERROR([bad value '$enable_backtrace' for --enable-backtrace]) + ;; +esac +if test "x$enable_backtrace" != xno; then + AC_DEFINE([HAVE_BACKTRACE_SCREEN], [1], [Define if the backtrace feature is enabled]) +fi +AM_CONDITIONAL([HAVE_BACKTRACE_SCREEN], [test x"$enable_backtrace" != xno]) + +if test "x$enable_backtrace" = xunwind-ptrace; then + AC_MSG_CHECKING([if libunwind has unw_get_elf_filename]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[/* unw_get_elf_filename might exist as a macro */ + #include ]], + [[static unw_cursor_t cursor; + /* First argument must be non-null */ + unw_get_elf_filename(&cursor, (char*)0, (size_t)0, (unw_word_t*)0);]] + )], + AC_DEFINE([HAVE_LIBUNWIND_ELF_FILENAME], [1], [Define if libunwind has unw_get_elf_filename]) + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no) + ) +fi AC_ARG_ENABLE( [hwloc], @@ -1619,6 +1677,7 @@ AC_MSG_RESULT([ unicode: $enable_unicode affinity: $enable_affinity unwind: $enable_unwind + backtrace: $enable_backtrace hwloc: $enable_hwloc debug: $enable_debug static: $enable_static From 78ffda34ee3f47885079fa8682f8bd44f191f8b5 Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Tue, 4 Nov 2025 22:01:27 +0100 Subject: [PATCH 2/5] Add libiberty library Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- README.md | 5 ++++ configure.ac | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42a2a3c41..fc7a5096c 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,11 @@ To install on the local system run `make install`. By default `make install` ins - default: *no* - possible values: - unwind-ptrace: use unwind-ptrace to get the backtraces + * `--enable-demangling`: + enable the demangling support for the backtraces + - default: *no* + - possible values: + - libiberty: use the libiberty to demangle the name ## Runtime dependencies: diff --git a/configure.ac b/configure.ac index 0ecd5aee1..4ab0debc7 100644 --- a/configure.ac +++ b/configure.ac @@ -1208,7 +1208,7 @@ if test "x$enable_backtrace" != xno; then fi AM_CONDITIONAL([HAVE_BACKTRACE_SCREEN], [test x"$enable_backtrace" != xno]) -if test "x$enable_backtrace" = xunwind-ptrace; then +if test "x$enable_backtrace" = xunwind-ptrace -a "x$my_htop_platform" = xlinux; then AC_MSG_CHECKING([if libunwind has unw_get_elf_filename]) AC_LINK_IFELSE( [AC_LANG_PROGRAM( @@ -1224,6 +1224,69 @@ if test "x$enable_backtrace" = xunwind-ptrace; then ) fi +AC_ARG_ENABLE( + [demangling], + [AS_HELP_STRING( + [--enable-demangling], + [enable demangling support for backtraces @<:@default=check@:>@] + )], + [], + [enable_demangling=check] +) + +have_libiberty=no +case $enable_demangling in + check|yes|libiberty) + AC_CHECK_LIB( + [iberty], + [cplus_demangle], + [ + have_libiberty=yes + AC_DEFINE([HAVE_LIBIBERTY], [1], [Define to 1 if libiberty is found.]) + LIBS="$LIBS -liberty" + ], [ + if test "x$enable_demangling" = xlibiberty; then + AC_MSG_ERROR([--enable-demangling specified but libiberty not found]) + fi + ] + ) + + if test "x$have_libiberty" = xyes; then + if ! htop_search_header_dir libiberty/demangle.h "/usr/include/libiberty"; then + have_libiberty=no + if test "x$enable_demangling" = xlibiberty; then + AC_MSG_ERROR([--enable-demangling specified but not found]) + fi + fi + fi + + if test "x$have_libiberty" = xyes; then + enable_demangling=libiberty + fi + ;; +esac + +case $enable_demangling in + libiberty) + AC_DEFINE([HAVE_DEMANGLING], [1], [Define to 1 if the demangling is supported.]) + ;; + check) + enable_demangling=no + ;; + yes) + AC_MSG_ERROR([cannot find any library for the backend supported by --enable-demangling]) + ;; + no) + ;; + *) + AC_MSG_ERROR([bad value '$enable_demangling' for --enable-demangling]) + ;; +esac + +if test "x$enable_backtrace" != xno && test "x$enable_demangling" = xno; then + AC_MSG_WARN([The backtrace feature is enabled, but name demangling is not.]) +fi + AC_ARG_ENABLE( [hwloc], [AS_HELP_STRING( @@ -1678,6 +1741,7 @@ AC_MSG_RESULT([ affinity: $enable_affinity unwind: $enable_unwind backtrace: $enable_backtrace + demangling: $enable_demangling hwloc: $enable_hwloc debug: $enable_debug static: $enable_static From 7e1a15fe19b6633ee244bff3d016b1d9d4300665 Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Tue, 17 Jun 2025 22:15:29 +0200 Subject: [PATCH 3/5] Add backtrace screen Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- Action.c | 41 ++++ BacktraceScreen.c | 526 ++++++++++++++++++++++++++++++++++++++++ BacktraceScreen.h | 85 +++++++ Makefile.am | 5 + darwin/Platform.c | 6 + darwin/Platform.h | 2 + dragonflybsd/Platform.c | 6 + dragonflybsd/Platform.h | 3 + freebsd/Platform.c | 6 + freebsd/Platform.h | 3 + linux/Platform.c | 121 +++++++++ linux/Platform.h | 4 + netbsd/Platform.c | 6 + netbsd/Platform.h | 3 + openbsd/Platform.c | 6 + openbsd/Platform.h | 3 + pcp/Platform.c | 6 + pcp/Platform.h | 3 + solaris/Platform.c | 6 + solaris/Platform.h | 3 + unsupported/Platform.c | 6 + unsupported/Platform.h | 3 + 22 files changed, 853 insertions(+) create mode 100644 BacktraceScreen.c create mode 100644 BacktraceScreen.h diff --git a/Action.c b/Action.c index 1d3bccc51..26f5514f8 100644 --- a/Action.c +++ b/Action.c @@ -27,7 +27,9 @@ in the source distribution for its full text. #include "ListItem.h" #include "Macros.h" #include "MainPanel.h" +#include "Object.h" #include "OpenFilesScreen.h" +#include "Panel.h" #include "Process.h" #include "ProcessLocksScreen.h" #include "ProvideCurses.h" @@ -47,6 +49,10 @@ in the source distribution for its full text. #include "AffinityPanel.h" #endif +#if defined(HAVE_BACKTRACE_SCREEN) +#include "BacktraceScreen.h" +#endif + Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow) { MainPanel* mainPanel = st->mainPanel; @@ -604,6 +610,35 @@ static Htop_Reaction actionShowLocks(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } +#if defined(HAVE_BACKTRACE_SCREEN) +static Htop_Reaction actionBacktrace(State *st) { + Process* selectedProcess = (Process *) Panel_getSelected((Panel *)st->mainPanel); + const Vector* allProcesses = st->mainPanel->super.items; + + Vector* processes = Vector_new(Class(Process), false, VECTOR_DEFAULT_SIZE); + if (!Process_isUserlandThread(selectedProcess)) { + for (int i = 0; i < Vector_size(allProcesses); i++) { + Process* process = (Process *)Vector_get(allProcesses, i); + if (Process_getThreadGroup(process) == Process_getThreadGroup(selectedProcess)) { + Vector_add(processes, process); + } + } + } else { + Vector_add(processes, selectedProcess); + } + + BacktracePanel* panel = BacktracePanel_new(processes, st->host->settings); + ScreenManager* screenManager = ScreenManager_new(NULL, st->host, st, false); + ScreenManager_add(screenManager, (Panel *)panel, 0); + + ScreenManager_run(screenManager, NULL, NULL, NULL); + BacktracePanel_delete((Object *)panel); + ScreenManager_delete(screenManager); + + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; +} +#endif + static Htop_Reaction actionStrace(State* st) { if (!Action_writeableProcess(st)) return HTOP_OK; @@ -687,6 +722,9 @@ static const struct { { .key = " F8 [: ", .roInactive = true, .info = "lower priority (+ nice)" }, #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) { .key = " a: ", .roInactive = true, .info = "set CPU affinity" }, +#endif +#if defined(HAVE_BACKTRACE_SCREEN) + { .key = " b: ", .roInactive = false, .info = "show process backtrace" }, #endif { .key = " e: ", .roInactive = false, .info = "show process environment" }, { .key = " i: ", .roInactive = true, .info = "set IO priority" }, @@ -931,6 +969,9 @@ void Action_setBindings(Htop_Action* keys) { keys['\\'] = actionIncFilter; keys[']'] = actionHigherPriority; keys['a'] = actionSetAffinity; +#if defined(HAVE_BACKTRACE_SCREEN) + keys['b'] = actionBacktrace; +#endif keys['c'] = actionTagAllChildren; keys['e'] = actionShowEnvScreen; keys['h'] = actionHelp; diff --git a/BacktraceScreen.c b/BacktraceScreen.c new file mode 100644 index 000000000..7298ea085 --- /dev/null +++ b/BacktraceScreen.c @@ -0,0 +1,526 @@ +/* +htop - BacktraceScreen.h +(C) 2025 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "BacktraceScreen.h" + +#if defined(HAVE_BACKTRACE_SCREEN) + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "Macros.h" +#include "Object.h" +#include "Panel.h" +#include "Platform.h" +#include "Process.h" +#include "ProvideCurses.h" +#include "RichString.h" +#include "Settings.h" +#include "Vector.h" +#include "XUtils.h" + + +#define MAX_HEX_ADDR_STR_LEN_32 (strlen("0x") + 8) +#define MAX_HEX_ADDR_STR_LEN_64 (strlen("0x") + 16) + +typedef enum BacktracePanelOptions_ { + OPTION_NAME_DEMANGLE, + OPTION_NAME_RAW, + OPTION_OBJECT_FULL_PATH, + OPTION_OBJECT_BASENAME, + LAST_PANEL_OPTION, +} BacktracePanelOptions; + +static const char* const BacktracePanel_options[LAST_PANEL_OPTION] = { + [OPTION_NAME_DEMANGLE] = "Demangle", + [OPTION_NAME_RAW] = "Raw", + [OPTION_OBJECT_FULL_PATH] = "Full Path", + [OPTION_OBJECT_BASENAME] = "Basename", +}; + +typedef enum BacktraceFrameHeaders_ { + HEADER_NUMBER_FRAME, + HEADER_ADDRESS, + HEADER_NAME, + HEADER_PATH, + LAST_PANEL_HEADER, +} BacktracePanelHeaders; + +static const char* const BacktraceFrame_headerFields[LAST_PANEL_HEADER] = { + [HEADER_NUMBER_FRAME] = "#", + [HEADER_ADDRESS] = "ADDRESS", + [HEADER_NAME] = "NAME", + [HEADER_PATH] = "PATH", +}; + +static const char* const BacktraceScreenFunctions[] = { + "Refresh", +#if defined(HAVE_DEMANGLING) + BacktracePanel_options[OPTION_NAME_RAW], +#endif + BacktracePanel_options[OPTION_OBJECT_FULL_PATH], + "Done ", + NULL +}; + +static const char* const BacktraceScreenKeys[] = { + "F1", +#if defined(HAVE_DEMANGLING) + "F2", +#endif + "F3", + "Esc", + NULL +}; + +static const int BacktraceScreenEvents[] = { + KEY_F(1), +#if defined(HAVE_DEMANGLING) + KEY_F(2), +#endif + KEY_F(3), + 27, +}; + +typedef enum BacktraceScreenDisplayOptions_ { + NO_OPTION = 0, + DEMANGLE_NAME_FUNCTION = 1 << 0, + SHOW_FULL_PATH_OBJECT = 1 << 1, +} BacktraceScreenDisplayOptions; + +BacktraceFrameData* BacktraceFrameData_new(void) { + BacktraceFrameData* this = AllocThis(BacktraceFrameData); + this->index = -1; + this->address = 0; + this->offset = 0; + this->functionName = NULL; + this->demangleFunctionName = NULL; + this->isSignalFrame = false; + this->objectPath = NULL; + this->objectName = NULL; + return this; +} + +void BacktraceFrameData_delete(Object* object) { + BacktraceFrameData* this = (BacktraceFrameData*)object; + free(this->functionName); + free(this->demangleFunctionName); + free(this->objectPath); + free(this->objectName); + free(this); +} + +static void BacktracePanel_displayHeader(BacktracePanel* this) { + const BacktracePanelPrintingHelper* printingHelper = &this->printingHelper; + const int displayOptions = this->displayOptions; + + size_t maxFunctionNameLength = printingHelper->maxFuncNameLen; + if (!!(displayOptions & DEMANGLE_NAME_FUNCTION) && + printingHelper->maxDemangledFuncNameLen > 0) { + maxFunctionNameLength = printingHelper->maxDemangledFuncNameLen; + } + + size_t maxObjLen = printingHelper->maxObjNameLen; + if (!!(displayOptions & SHOW_FULL_PATH_OBJECT)) { + maxObjLen = printingHelper->maxObjPathLen; + } + + /* + * The parameters for printf are of type int. + * A check is needed to prevent integer overflow. + */ + assert(printingHelper->maxFrameNumLen <= INT_MAX); + assert(printingHelper->maxAddrLen <= INT_MAX); + assert(printingHelper->maxDemangledFuncNameLen <= INT_MAX); + assert(maxObjLen <= INT_MAX); + assert(maxFunctionNameLength <= INT_MAX); + + char* line = NULL; + xAsprintf(&line, "%*s %-*s %-*s %-*s", + (int)printingHelper->maxFrameNumLen, BacktraceFrame_headerFields[HEADER_NUMBER_FRAME], + (int)printingHelper->maxAddrLen, BacktraceFrame_headerFields[HEADER_ADDRESS], + (int)maxObjLen, BacktraceFrame_headerFields[HEADER_PATH], + (int)maxFunctionNameLength, BacktraceFrame_headerFields[HEADER_NAME] + ); + + Panel_setHeader((Panel*)this, line); + free(line); +} + +static void BacktracePanel_makePrintingHelper(const BacktracePanel* this, BacktracePanelPrintingHelper* printingHelper) { + Vector* lines = this->super.items; + size_t longestAddress = 0; + + for (int i = 0; i < Vector_size(lines); i++) { + const BacktracePanelRow* row = (const BacktracePanelRow*)Vector_get(lines, i); + if (row->type != BACKTRACE_PANEL_ROW_DATA_FRAME) { + continue; + } + + size_t digitOfOffsetFrame = strlen("+0x") + countDigits(row->data.frame->offset, 16); + if (row->data.frame->demangleFunctionName) { + size_t demangledFunctionNameLength = strlen(row->data.frame->demangleFunctionName) + digitOfOffsetFrame; + printingHelper->maxDemangledFuncNameLen = MAXIMUM(demangledFunctionNameLength, printingHelper->maxDemangledFuncNameLen); + } + + if (row->data.frame->functionName) { + size_t functionNameLength = strlen(row->data.frame->functionName) + digitOfOffsetFrame; + printingHelper->maxFuncNameLen = MAXIMUM(functionNameLength, printingHelper->maxFuncNameLen); + } + + if (row->data.frame->objectPath) { + size_t objectPathLength = strlen(row->data.frame->objectPath); + printingHelper->maxObjPathLen = MAXIMUM(objectPathLength, printingHelper->maxObjPathLen); + } + + if (row->data.frame->objectName) { + size_t objectNameLength = strlen(row->data.frame->objectName); + printingHelper->maxObjNameLen = MAXIMUM(objectNameLength, printingHelper->maxObjNameLen); + } + + printingHelper->maxFrameNumLen = MAXIMUM(countDigits(row->data.frame->index, 10), printingHelper->maxFrameNumLen); + + longestAddress = MAXIMUM(row->data.frame->address, longestAddress); + } + + size_t addressLength = MAX_HEX_ADDR_STR_LEN_32; + if (longestAddress > UINT32_MAX) { + addressLength = MAX_HEX_ADDR_STR_LEN_64; + } + printingHelper->maxAddrLen = addressLength; +} + +static void BacktracePanel_populateFrames(BacktracePanel* this) { + char* error = NULL; + + Vector* data = Vector_new(Class(BacktraceFrameData), false, VECTOR_DEFAULT_SIZE); + for (int i = 0; i < Vector_size(this->processes); i++) { + const Process* process = (Process*)Vector_get(this->processes, i); + Platform_getBacktrace(Process_getPid(process), data, &error); + + if (error) { + BacktracePanelRow* errorRow = BacktracePanelRow_new(this); + errorRow->type = BACKTRACE_PANEL_ROW_ERROR; + errorRow->data.error = error; + + Panel_prune((Panel*)this); + Panel_add((Panel*)this, (Object*)errorRow); + Vector_delete(data); + return; + } + + BacktracePanelRow* header = BacktracePanelRow_new(this); + header->process = process; + header->type = BACKTRACE_PANEL_ROW_PROCESS_INFORMATION; + Panel_add((Panel*)this, (Object*)header); + + for (int j = 0; j < Vector_size(data); j++) { + BacktracePanelRow* row = BacktracePanelRow_new(this); + row->type = BACKTRACE_PANEL_ROW_DATA_FRAME; + row->data.frame = (BacktraceFrameData*)Vector_get(data, j); + row->process = process; + + Panel_add((Panel*)this, (Object*)row); + } + Vector_prune(data); + } + Vector_delete(data); + + BacktracePanelPrintingHelper* printingHelper = &this->printingHelper; + BacktracePanel_makePrintingHelper(this, printingHelper); + BacktracePanel_displayHeader(this); +} + +static HandlerResult BacktracePanel_eventHandler(Panel* super, int ch) { + BacktracePanel* this = (BacktracePanel*)super; + int* const displayOptions = &this->displayOptions; + + HandlerResult result = IGNORED; + switch (ch) { + case KEY_F(1): + Panel_prune(super); + BacktracePanel_populateFrames(this); + break; + +#if defined(HAVE_DEMANGLING) + case KEY_F(2): + if (!!(*displayOptions & DEMANGLE_NAME_FUNCTION)) { + *displayOptions &= ~DEMANGLE_NAME_FUNCTION; + FunctionBar_setLabel(super->defaultBar, KEY_F(2), BacktracePanel_options[OPTION_NAME_DEMANGLE]); + } else { + *displayOptions |= DEMANGLE_NAME_FUNCTION; + FunctionBar_setLabel(super->defaultBar, KEY_F(2), BacktracePanel_options[OPTION_NAME_RAW]); + } + this->super.needsRedraw = true; + break; +#endif + + case 'p': + case KEY_F(3): + if (!!(*displayOptions & SHOW_FULL_PATH_OBJECT)) { + *displayOptions &= ~SHOW_FULL_PATH_OBJECT; + FunctionBar_setLabel(super->defaultBar, KEY_F(3), BacktracePanel_options[OPTION_OBJECT_FULL_PATH]); + } else { + FunctionBar_setLabel(super->defaultBar, KEY_F(3), BacktracePanel_options[OPTION_OBJECT_BASENAME]); + *displayOptions |= SHOW_FULL_PATH_OBJECT; + } + this->super.needsRedraw = true; + BacktracePanel_displayHeader(this); + break; + } + return result; +} + +BacktracePanel* BacktracePanel_new(Vector* processes, const Settings* settings) { + BacktracePanel* this = AllocThis(BacktracePanel); + this->processes = processes; + + this->printingHelper.maxAddrLen = 0; + this->printingHelper.maxDemangledFuncNameLen = 0; + this->printingHelper.maxFrameNumLen = 0; + this->printingHelper.maxFuncNameLen = 0; + this->printingHelper.maxObjNameLen = 0; + this->printingHelper.maxObjPathLen = 0; + + this->displayOptions = DEMANGLE_NAME_FUNCTION; + this->settings = settings; + + Panel* super = (Panel*) this; + Panel_init(super, 1, 1, 0, 1, Class(BacktracePanelRow), true, + FunctionBar_new(BacktraceScreenFunctions, BacktraceScreenKeys, BacktraceScreenEvents) + ); + + BacktracePanel_populateFrames(this); + + if (settings->showProgramPath) { + this->displayOptions |= SHOW_FULL_PATH_OBJECT; + FunctionBar_setLabel(super->defaultBar, KEY_F(3), BacktracePanel_options[OPTION_OBJECT_BASENAME]); + } else { + this->displayOptions &= ~SHOW_FULL_PATH_OBJECT; + FunctionBar_setLabel(super->defaultBar, KEY_F(3), BacktracePanel_options[OPTION_OBJECT_FULL_PATH]); + } + + return this; +} + +void BacktracePanel_delete(Object* object) { + BacktracePanel* this = (BacktracePanel*)object; + Vector_delete(this->processes); + Panel_delete(object); +} + +static void BacktracePanelRow_highlightBasename(const BacktracePanelRow* row, RichString* out, char* line, int objectPathStart) { + assert(row); + assert(row->type == BACKTRACE_PANEL_ROW_DATA_FRAME); + assert(objectPathStart >= 0); + + const Process* process = row->process; + + char* procExe = process->procExe ? process->procExe + process->procExeBasenameOffset : NULL; + if (!procExe) { + return; + } + + size_t endBasenameIndex = objectPathStart; + size_t lastSlashBasenameIndex = objectPathStart; + for (; line[endBasenameIndex] != ' '; endBasenameIndex++) { + if (line[endBasenameIndex] == '/') { + lastSlashBasenameIndex = endBasenameIndex + 1; + } + } + + size_t sizeBasename = endBasenameIndex - lastSlashBasenameIndex; + if (strncmp(line + lastSlashBasenameIndex, procExe, sizeBasename) == 0) { + RichString_setAttrn(out, CRT_colors[PROCESS_BASENAME], lastSlashBasenameIndex, sizeBasename); + } +} + +static void BacktracePanelRow_displayInformation(const Object* super, RichString* out) { + const BacktracePanelRow* row = (const BacktracePanelRow*)super; + assert(row); + assert(row->type == BACKTRACE_PANEL_ROW_PROCESS_INFORMATION); + + const Process* process = row->process; + + char* informations = NULL; + int colorBasename = DEFAULT_COLOR; + int indexProcessComm = -1; + int len = -1; + size_t highlightLen = 0; + size_t highlightOffset = 0; + + for (size_t i = 0; i < process->mergedCommand.highlightCount; i++) { + const ProcessCmdlineHighlight* highlight = process->mergedCommand.highlights; + if (!!(highlight->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)) { + highlightLen = highlight->length; + highlightOffset = highlight->offset; + break; + } + } + + if (highlightLen == 0) { + highlightLen = strlen(process->mergedCommand.str); + } + + if (Process_isThread(process)) { + colorBasename = PROCESS_THREAD_BASENAME; + len = xAsprintf(&informations, "Thread %d: %n%s", Process_getPid(process), &indexProcessComm, process->mergedCommand.str); + } else { + colorBasename = PROCESS_BASENAME; + len = xAsprintf(&informations, "Process %d: %n%s",Process_getPid(process), &indexProcessComm, process->mergedCommand.str); + } + + RichString_appendnAscii(out, CRT_colors[DEFAULT_COLOR] | A_BOLD, informations, len); + if (indexProcessComm != -1) { + RichString_setAttrn(out, CRT_colors[colorBasename] | A_BOLD, indexProcessComm + highlightOffset, highlightLen); + } + + free(informations); +} + +static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) { + const BacktracePanelRow* row = (const BacktracePanelRow*)super; + assert(row); + assert(row->type == BACKTRACE_PANEL_ROW_DATA_FRAME); + + const BacktracePanelPrintingHelper* printingHelper = row->printingHelper; + const int* const displayOptions = row->displayOptions; + const BacktraceFrameData* frame = row->data.frame; + + char* functionName = frame->functionName; + int maxFunctionNameLength = printingHelper->maxFuncNameLen; + if (!!(*displayOptions & DEMANGLE_NAME_FUNCTION) && + printingHelper->maxDemangledFuncNameLen > 0) { + maxFunctionNameLength = printingHelper->maxDemangledFuncNameLen; + if (frame->demangleFunctionName) { + functionName = frame->demangleFunctionName; + } + } + + char* completeFunctionName = NULL; + xAsprintf(&completeFunctionName, "%s+0x%zx", functionName, frame->offset); + + char* objectDisplayed = frame->objectName; + size_t objectLength = printingHelper->maxObjNameLen; + if (!!(*displayOptions & SHOW_FULL_PATH_OBJECT)) { + objectDisplayed = frame->objectPath; + objectLength = printingHelper->maxObjPathLen; + } + + size_t maxAddrLen = printingHelper->maxAddrLen - strlen("0x"); + char* line = NULL; + int objectPathStart = -1; + + /* + * The parameters for printf are of type int. + * A check is needed to prevent integer overflow. + */ + assert(printingHelper->maxFrameNumLen <= INT_MAX); + assert(maxAddrLen <= INT_MAX); + assert(maxFunctionNameLength <= INT_MAX); + assert(objectLength <= INT_MAX); + + int len = xAsprintf(&line, "%*d 0x%0*zx %n%-*s %-*s", + (int)printingHelper->maxFrameNumLen, frame->index, + (int)maxAddrLen, frame->address, + &objectPathStart, + (int)objectLength, objectDisplayed ? objectDisplayed : "-", + (int)maxFunctionNameLength, completeFunctionName + ); + + int colors = CRT_colors[DEFAULT_COLOR]; + if (!objectDisplayed && row->data.frame->address == 0) { + colors = CRT_colors[DYNAMIC_GRAY]; + } + + RichString_appendnAscii(out, colors, line, len); + + if (row->settings->highlightBaseName) { + BacktracePanelRow_highlightBasename(row, out, line, objectPathStart); + } + + free(completeFunctionName); + free(line); +} + +static void BacktracePanelRow_displayError(const Object* super, RichString* out) { + const BacktracePanelRow* row = (const BacktracePanelRow*)super; + assert(row); + assert(row->type == BACKTRACE_PANEL_ROW_ERROR); + assert(row->data.error); + + RichString_appendAscii(out, CRT_colors[DEFAULT_COLOR], row->data.error); +} + +static void BacktracePanelRow_display(const Object* super, RichString* out) { + const BacktracePanelRow* row = (const BacktracePanelRow*)super; + assert(row); + + switch (row->type) { + case BACKTRACE_PANEL_ROW_DATA_FRAME: + BacktracePanelRow_displayFrame(super, out); + break; + case BACKTRACE_PANEL_ROW_PROCESS_INFORMATION: + BacktracePanelRow_displayInformation(super, out); + break; + case BACKTRACE_PANEL_ROW_ERROR: + BacktracePanelRow_displayError(super, out); + break; + } +} + +BacktracePanelRow* BacktracePanelRow_new(const BacktracePanel* panel) { + BacktracePanelRow* this = AllocThis(BacktracePanelRow); + this->displayOptions = &panel->displayOptions; + this->printingHelper = &panel->printingHelper; + this->settings = panel->settings; + return this; +} + +void BacktracePanelRow_delete(Object* object) { + BacktracePanelRow* this = (BacktracePanelRow*)object; + switch (this->type) { + case BACKTRACE_PANEL_ROW_DATA_FRAME: + BacktraceFrameData_delete((Object *)this->data.frame); + break; + case BACKTRACE_PANEL_ROW_ERROR: + free(this->data.error); + break; + + } + free(this); +} + +const ObjectClass BacktraceFrameData_class = { + .extends = Class(Object), + .delete = BacktraceFrameData_delete, +}; + +const PanelClass BacktracePanel_class = { + .super = { + .extends = Class(Panel), + .delete = BacktracePanel_delete, + }, + .eventHandler = BacktracePanel_eventHandler, +}; + +const ObjectClass BacktracePanelRow_class = { + .extends = Class(Object), + .delete = BacktracePanelRow_delete, + .display = BacktracePanelRow_display, +}; +#endif diff --git a/BacktraceScreen.h b/BacktraceScreen.h new file mode 100644 index 000000000..adbbc8940 --- /dev/null +++ b/BacktraceScreen.h @@ -0,0 +1,85 @@ +#ifndef HEADER_BacktraceScreen +#define HEADER_BacktraceScreen +/* +htop - BacktraceScreen.h +(C) 2025 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "Object.h" +#include "Panel.h" +#include "Process.h" +#include "Settings.h" +#include "Vector.h" + +typedef struct BacktraceFrameData_ { + Object super; + + int index; + size_t address; + size_t offset; + char* functionName; + char* demangleFunctionName; + bool isSignalFrame; + char* objectPath; + char* objectName; +} BacktraceFrameData; + +typedef struct BacktracePanelPrintingHelper_ { + size_t maxAddrLen; + size_t maxDemangledFuncNameLen; + size_t maxFuncNameLen; + size_t maxFrameNumLen; + size_t maxObjPathLen; + size_t maxObjNameLen; +} BacktracePanelPrintingHelper; + +typedef struct BacktracePanel_ { + Panel super; + + Vector* processes; + BacktracePanelPrintingHelper printingHelper; + int displayOptions; + const Settings* settings; +} BacktracePanel; + +typedef enum BacktracePanelRowType_ { + BACKTRACE_PANEL_ROW_DATA_FRAME, + BACKTRACE_PANEL_ROW_ERROR, + BACKTRACE_PANEL_ROW_PROCESS_INFORMATION +} BacktracePanelRowType; + +typedef struct BacktracePanelRow_ { + Object super; + + int type; + union { + BacktraceFrameData* frame; + char* error; + } data; + + const int* displayOptions; + const BacktracePanelPrintingHelper* printingHelper; + const Process* process; + const Settings* settings; +} BacktracePanelRow; + +BacktraceFrameData* BacktraceFrameData_new(void); +void BacktraceFrameData_delete(Object* object); + +BacktracePanel* BacktracePanel_new(Vector* processes, const Settings* settings); +void BacktracePanel_delete(Object* object); + +BacktracePanelRow* BacktracePanelRow_new(const BacktracePanel* panel); +void BacktracePanelRow_delete(Object *object); + +extern const ObjectClass BacktraceFrameData_class; + +extern const PanelClass BacktracePanel_class; +extern const ObjectClass BacktracePanelRow_class; + +#endif diff --git a/Makefile.am b/Makefile.am index cf2ccee43..4b88da287 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,6 +166,11 @@ myhtopheaders = \ Vector.h \ XUtils.h +if HAVE_BACKTRACE_SCREEN +myhtopheaders += BacktraceScreen.h +myhtopsources += BacktraceScreen.c +endif + # Linux # ----- diff --git a/darwin/Platform.c b/darwin/Platform.c index aa7400c87..74ad156ba 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -650,6 +650,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/darwin/Platform.h b/darwin/Platform.h index 8fe7d7d41..ed1576ea5 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -78,6 +78,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index ddd211771..c79954609 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -340,6 +340,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { int life; size_t life_len = sizeof(life); diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index 6977ec3e8..e12e3b04a 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -14,6 +14,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -67,6 +68,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 0eda02b7a..a2df86ca5 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -373,6 +373,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { int life; size_t life_len = sizeof(life); diff --git a/freebsd/Platform.h b/freebsd/Platform.h index 9e33fd929..24cfef014 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -11,6 +11,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -67,6 +68,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/linux/Platform.c b/linux/Platform.c index 352c9c80b..cc391bed8 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -14,6 +14,7 @@ in the source distribution for its full text. #include #include #include +#include #include #include #include @@ -21,8 +22,12 @@ in the source distribution for its full text. #include #include #include +#include #include +#include +#include +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "ClockMeter.h" #include "Compat.h" @@ -51,6 +56,7 @@ in the source distribution for its full text. #include "SysArchMeter.h" #include "TasksMeter.h" #include "UptimeMeter.h" +#include "Vector.h" #include "XUtils.h" #include "linux/IOPriority.h" #include "linux/IOPriorityPanel.h" @@ -69,6 +75,14 @@ in the source distribution for its full text. #include #endif +#ifdef HAVE_LIBIBERTY +#include +#endif + +#ifdef HAVE_LIBUNWIND_PTRACE +#include +#endif + #ifdef HAVE_SENSORS_SENSORS_H #include "LibSensors.h" #endif @@ -726,6 +740,113 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { +#ifdef HAVE_LIBUNWIND_PTRACE + *error = NULL; + + unw_addr_space_t addrSpace = unw_create_addr_space(&_UPT_accessors, 0); + if (!addrSpace) { + xAsprintf(error, "Unable to initialize libunwind."); + return; + } + + if (pid <= 0) { + xAsprintf(error, "Unable to get the pid"); + goto addr_space_error; + } + + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + int ptraceErrno = errno; + xAsprintf(error, "ptrace: %s (%d)", strerror(ptraceErrno), ptraceErrno); + goto addr_space_error; + } + + int waitStatus = 0; + if (wait(&waitStatus) == -1) { + int waitErrno = errno; + xAsprintf(error, "wait: %s (%d)", strerror(waitErrno), waitErrno); + goto ptrace_error; + } + + if (WIFSTOPPED(waitStatus) == 0) { + *error = xStrdup("The process chosen is not stopped correctly."); + goto ptrace_error; + } + + struct UPT_info* context = _UPT_create(pid); + if (!context) { + xAsprintf(error, "Unable to create the context of libunwind-ptrace"); + goto ptrace_error; + } + + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, addrSpace, context); + if (ret < 0) { + xAsprintf(error, "libunwind cursor: ret=%d", ret); + goto context_error; + } + + int index = 0; + do { + char procName[2048] = "?"; + unw_word_t offset; + unw_word_t pc; + + BacktraceFrameData* frame = BacktraceFrameData_new(); + frame->index = index; + if (unw_get_proc_name(&cursor, procName, sizeof(procName), &offset) == 0) { + ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (ret < 0) { + xAsprintf(error, "unable to get program counter register: %d", ret); + BacktraceFrameData_delete((Object *)frame); + break; + } + + frame->address = pc; + frame->offset = offset; + frame->isSignalFrame = unw_is_signal_frame(&cursor); + + frame->functionName = xStrndup(procName, 2048); + +#if defined(HAVE_LIBIBERTY) + char* demangledName = cplus_demangle(frame->functionName, + DMGL_PARAMS | AUTO_DEMANGLING); + frame->demangleFunctionName = demangledName; +#endif + +#if defined(HAVE_LIBUNWIND_ELF_FILENAME) + unw_word_t offsetElfFileName; + char elfFileName[2048] = { 0 }; + if (unw_get_elf_filename(&cursor, elfFileName, sizeof(elfFileName), &offsetElfFileName) == 0) { + frame->objectPath = xStrndup(elfFileName, 2048); + + char *lastSlash = strrchr(frame->objectPath, '/'); + frame->objectName = xStrndup(lastSlash + 1, 2048); + } +#endif + + } else { + frame->functionName = xStrdup("???"); + } + Vector_add(frames, (Object *)frame); + index++; + } while (unw_step(&cursor) > 0); + +context_error: + _UPT_destroy(context); + +ptrace_error: + ptrace(PTRACE_DETACH, pid, 0, 0); + +addr_space_error: + unw_destroy_addr_space(addrSpace); +#else /* !HAVE_LIBUNWIND_PTRACE */ + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +#endif +} + // Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com). #define PROC_BATTERY_DIR PROCDIR "/acpi/battery" diff --git a/linux/Platform.h b/linux/Platform.h index f1cd29158..b118b16e4 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -15,6 +15,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -28,6 +29,7 @@ in the source distribution for its full text. #include "Settings.h" #include "SignalsPanel.h" #include "CommandLine.h" +#include "Vector.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -88,6 +90,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/netbsd/Platform.c b/netbsd/Platform.c index af2a8613d..e687b3798 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -432,6 +432,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { prop_dictionary_t dict, fields, props; prop_object_t device, class; diff --git a/netbsd/Platform.h b/netbsd/Platform.h index d46112085..db74e620c 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -17,6 +17,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Meter.h" @@ -73,6 +74,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 7dc5b4de6..61fa3f09d 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -342,6 +342,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + static bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, size_t* sdlen) { for (int devn = 0;; devn++) { mib[2] = devn; diff --git a/openbsd/Platform.h b/openbsd/Platform.h index 6cb3f304a..50fdd47d1 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -65,6 +66,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/pcp/Platform.c b/pcp/Platform.c index e779ea0bc..9723fa6d4 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -780,6 +780,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getFileDescriptors(double* used, double* max) { *used = NAN; *max = 65536; diff --git a/pcp/Platform.h b/pcp/Platform.h index c33d63b27..a4c0a60e9 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -25,6 +25,7 @@ in the source distribution for its full text. #undef PACKAGE_BUGREPORT #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -109,6 +110,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); void Platform_getHostname(char* buffer, size_t size); diff --git a/solaris/Platform.c b/solaris/Platform.c index 3934f7896..3b949c945 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -336,6 +336,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/solaris/Platform.h b/solaris/Platform.h index 9f53215a3..ecde17596 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -27,6 +27,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "CommandLine.h" #include "DiskIOMeter.h" @@ -92,6 +93,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/unsupported/Platform.c b/unsupported/Platform.c index dbfddd916..909a46551 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -151,6 +151,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error) { + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/unsupported/Platform.h b/unsupported/Platform.h index a3ba6f470..f7dd9676c 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -12,6 +12,7 @@ in the source distribution for its full text. #include #include "Action.h" +#include "BacktraceScreen.h" #include "BatteryMeter.h" #include "DiskIOMeter.h" #include "Hashtable.h" @@ -61,6 +62,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(pid_t pid, Vector* frames, char** error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); void Platform_getHostname(char* buffer, size_t size); From 1d87ea234a8d21e1106b6b692b7c9cb9eee7df2d Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Wed, 24 Apr 2024 19:39:46 +0200 Subject: [PATCH 4/5] Add backtrace Linux dependencies for the CI. Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e47f7b764..74f115aa2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,15 +60,15 @@ jobs: steps: - uses: actions/checkout@v5 - name: Install Dependencies - run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libiberty-dev libunwind-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck - run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities' + run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty' build-ubuntu-latest-full-featured-clang: runs-on: ubuntu-latest @@ -83,15 +83,15 @@ jobs: sudo add-apt-repository "deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-18 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-18 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-18 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libiberty-dev libunwind-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Distcheck - run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities' + run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty' build-ubuntu-latest-gcc-static: runs-on: ubuntu-latest @@ -153,11 +153,11 @@ jobs: sudo add-apt-repository "deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-18 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-18 clang-tools-18 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-18 clang-tools-18 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libiberty-dev libunwind-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: scan-build-18 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) + run: scan-build-18 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty || ( cat config.log; exit 1; ) - name: Build run: scan-build-18 -analyze-headers --status-bugs make -j"$(nproc)" @@ -182,11 +182,11 @@ jobs: - name: Install LLVM Toolchain run: sudo apt-get install --no-install-recommends clang-18 libclang-rt-18-dev llvm-18 - name: Install Dependencies - run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libiberty-dev libunwind-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; ) + run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities --enable-backtrace --enable-demangling=libiberty || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Run sanitized htop (1) From a90c62be68e8e03efcb77d734b132d288ca037c1 Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Wed, 24 Apr 2024 20:15:23 +0200 Subject: [PATCH 5/5] Update the man page for backtrace screen Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- htop.1.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/htop.1.in b/htop.1.in index 0e7fa5db8..427b460e7 100644 --- a/htop.1.in +++ b/htop.1.in @@ -273,6 +273,10 @@ Pause/resume process updates. .B m Merge exe, comm and cmdline, where applicable. (This is a toggle key.) .TP +.B b +Show the backtrace of a process. (This feature requires enabling +at the compile time, and currently htop supports this feature in Linux only.) +.TP .B Ctrl-L Refresh: redraw screen and recalculate values. .TP