From db1f2b77b7a3b51657ae8df5a8d8e17712ff59c5 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 2 Feb 2026 20:59:40 +0800 Subject: [PATCH 1/9] Adjust configure.ac comments (about libunwind) * Explain that "/usr/include/libunwind/libunwind.h" is a Debian package thing. (LLVM libunwind upstream doesn't install the headers in that strange location.) (See also 6388033e107a74a6e962eabf4ec06065d12d28db and issue #894) * Clarifications on why we define UNW_LOCAL_ONLY during redection of libunwind functions. No change in configure script behavior. Signed-off-by: Kang-Che Sung --- configure.ac | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 768cd4748..e53bb08c1 100644 --- a/configure.ac +++ b/configure.ac @@ -1010,6 +1010,11 @@ case "$enable_unwind" in htop_save_CFLAGS=$CFLAGS CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS $CFLAGS" + # Debian installs LLVM libunwind headers in the non-standard + # "/usr/include/libunwind" directory. + # (Package name: "libunwind-${llvm_version}-dev") + # libunwind (HP) headers can be found in the default include + # directories in Debian. (Package name: "libunwind-dev") if htop_search_header_dir libunwind.h "/usr/include/libunwind"; then AC_DEFINE([HAVE_LIBUNWIND_H], 1, [Define to 1 if you have the header file.]) elif test "$enable_unwind" = yes; then @@ -1033,8 +1038,10 @@ if test "$enable_unwind" != no; then CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS $CFLAGS" LIBS="$LIBUNWIND_LIBS $LIBS" - # unw_init_local() is a macro in HP's implementation of libunwind - # (the most popular one, Mosberger et al.) + # Almost all unw_*() functions are defined as macros in HP's + # implementation of libunwind (Mosberger et al.). + # Define UNW_LOCAL_ONLY to avoid unnecessary dependency on + # "-lunwind-generic" when linking. AC_MSG_CHECKING([whether $LIBUNWIND_LIBS supports local unwinding]) AC_LINK_IFELSE( [AC_LANG_PROGRAM( From 3789aadb6d12d2f0a0a789d327b8f11a113c0f0d Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 2 Feb 2026 21:00:34 +0800 Subject: [PATCH 2/9] build: Rename '--enable-unwind' to '--with-libunwind' The configure option '--enable-unwind' doesn't actually enable a feature in htop but selects a library dependency for a feature (printing local backtrace for this case). Use a '--with-' naming to reflect the option's purpose better. For backward compatibility (with automated build scripts), using '--enable-unwind' will still work but it will print a warning. Signed-off-by: Kang-Che Sung --- configure.ac | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/configure.ac b/configure.ac index e53bb08c1..d95a74f1c 100644 --- a/configure.ac +++ b/configure.ac @@ -984,16 +984,21 @@ $3 return "$htop_header_search_status" } # htop_search_header_dir -AC_ARG_ENABLE( - [unwind], +AC_ARG_WITH( + [libunwind], [AS_HELP_STRING( - [--enable-unwind], - [enable unwind support for printing backtraces; requires libunwind @<:@default=check@:>@] + [--with-libunwind], + [use libunwind for printing local backtrace @<:@default=check@:>@] )], [], - [enable_unwind=check] + [ + with_libunwind=${enable_unwind-'check'} + if test "x${enable_unwind+y}" = xy; then + AC_MSG_WARN([--enable-unwind is deprecated; use --with-libunwind instead]) + fi + ] ) -case "$enable_unwind" in +case $with_libunwind in no) ;; check|yes) @@ -1017,20 +1022,20 @@ case "$enable_unwind" in # directories in Debian. (Package name: "libunwind-dev") if htop_search_header_dir libunwind.h "/usr/include/libunwind"; then AC_DEFINE([HAVE_LIBUNWIND_H], 1, [Define to 1 if you have the header file.]) - elif test "$enable_unwind" = yes; then + elif test "$with_libunwind" = yes; then AC_MSG_ERROR([cannot find required header file libunwind.h]) else - enable_unwind=no + with_libunwind=no fi CFLAGS=$htop_save_CFLAGS ;; *) - AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind]) + AC_MSG_ERROR([bad value '$with_libunwind' for --with-libunwind]) ;; esac -if test "$enable_unwind" != no; then +if test "$with_libunwind" != no; then htop_save_CPPFLAGS=$CPPFLAGS htop_save_CFLAGS=$CFLAGS htop_save_LIBS=$LIBS @@ -1082,21 +1087,21 @@ if test "$enable_unwind" != no; then fi if test "$libunwind_local_support" = no; then AC_MSG_RESULT([no]) - if test "$enable_unwind" = yes; then + if test "$with_libunwind" = yes; then AC_MSG_WARN([this build of libunwind might not support local unwinding]) else LIBS=$htop_save_LIBS - enable_unwind=no + with_libunwind=no fi fi ], [ AC_MSG_RESULT([no]) - if test "$enable_unwind" = yes; then + if test "$with_libunwind" = yes; then AC_MSG_FAILURE([cannot link with libunwind]) else LIBS=$htop_save_LIBS - enable_unwind=no + with_libunwind=no fi ] ) @@ -1105,9 +1110,9 @@ if test "$enable_unwind" != no; then CFLAGS=$htop_save_CFLAGS fi -if test "$enable_unwind" != no; then +if test "$with_libunwind" != no; then AM_CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS" - enable_unwind=yes + with_libunwind=yes AC_DEFINE([HAVE_LOCAL_UNWIND], 1, [Define to 1 if local unwinding is enabled.]) else # Fall back to backtrace(3) and add -lexecinfo if needed @@ -1611,7 +1616,6 @@ AC_MSG_RESULT([ (Linux) capabilities: $enable_capabilities unicode: $enable_unicode affinity: $enable_affinity - unwind: $enable_unwind hwloc: $enable_hwloc debug: $enable_debug static: $enable_static From ba5707597dc9827cb461eb7d3274dcbdd48ea372 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 2 Feb 2026 21:04:44 +0800 Subject: [PATCH 3/9] build: Add '--enable-backtrace' configure option For the remote backtrace feature that would be introduced in the next commit. For now the feature supports only one backend (libunwind-ptrace). This commit adds checks to libunwind-ptrace. Co-authored-by: Odric Roux-Paris Co-authored-by: Benny Baumann Signed-off-by: Kang-Che Sung --- configure.ac | 129 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index d95a74f1c..9c540553d 100644 --- a/configure.ac +++ b/configure.ac @@ -984,6 +984,28 @@ $3 return "$htop_header_search_status" } # htop_search_header_dir +AC_ARG_ENABLE( + [backtrace], + [AS_HELP_STRING( + [--enable-backtrace=BACKEND], + [enable showing backtraces of a processes; available backends: unwind-ptrace @<:@default=no@:>@] + )], + [], + [enable_backtrace=no] +) + +backtrace_backend_found=no +have_libunwind_ptrace=no +case $enable_backtrace in + check|yes) + have_libunwind_ptrace=check + ;; + unwind-ptrace|libunwind-ptrace) + have_libunwind_ptrace=check + enable_backtrace=unwind-ptrace + ;; +esac + AC_ARG_WITH( [libunwind], [AS_HELP_STRING( @@ -998,19 +1020,52 @@ AC_ARG_WITH( fi ] ) + +default_libunwind_libs='-lunwind' case $with_libunwind in no) ;; check|yes) - HTOP_PKG_CHECK_MODULES(LIBUNWIND, libunwind, - [], - [ - if test "$enable_static" = yes; then - AC_CHECK_LIB([lzma], [lzma_index_buffer_decode]) - fi - : "${LIBUNWIND_LIBS='-lunwind'}" - ] - ) + libunwind_ptrace_name='' + if test "$have_libunwind_ptrace" != no; then + libunwind_ptrace_name='libunwind-ptrace ' + fi + + while :; do + HTOP_PKG_CHECK_MODULES( + [LIBUNWIND], + [${libunwind_ptrace_name}libunwind], + [ + if test "$have_libunwind_ptrace" != no; then + enable_backtrace=unwind-ptrace + fi + break + ], [ + test "x$libunwind_ptrace_name" != x || break + libunwind_ptrace_name='' + ] + ) + done + + if test "x${LIBUNWIND_LIBS+y}" = x; then + if test "$enable_static" = yes; then + AC_CHECK_LIB( + [lzma], + [lzma_index_buffer_decode], + [default_libunwind_libs='-lunwind -llzma'] + ) + fi + + if test "$have_libunwind_ptrace" != no; then + AC_CHECK_LIB( + [unwind-ptrace], + [_UPT_create], + [:], + [have_libunwind_ptrace=no], + [-lunwind-generic $default_libunwind_libs] + ) + fi + fi htop_save_CFLAGS=$CFLAGS CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS $CFLAGS" @@ -1022,10 +1077,18 @@ case $with_libunwind in # directories in Debian. (Package name: "libunwind-dev") if htop_search_header_dir libunwind.h "/usr/include/libunwind"; then AC_DEFINE([HAVE_LIBUNWIND_H], 1, [Define to 1 if you have the header file.]) + if test "$have_libunwind_ptrace" != no; then + AC_CHECK_HEADER( + [libunwind-ptrace.h], + [:], + [have_libunwind_ptrace=no] + ) + fi elif test "$with_libunwind" = yes; then AC_MSG_ERROR([cannot find required header file libunwind.h]) else with_libunwind=no + have_libunwind_ptrace=no fi CFLAGS=$htop_save_CFLAGS @@ -1036,6 +1099,13 @@ case $with_libunwind in esac if test "$with_libunwind" != no; then + if test "x${LIBUNWIND_LIBS+y}" = x; then + if test "$have_libunwind_ptrace" != no; then + default_libunwind_libs="-lunwind-ptrace -lunwind-generic $default_libunwind_libs" + fi + LIBUNWIND_LIBS=$default_libunwind_libs + fi + htop_save_CPPFLAGS=$CPPFLAGS htop_save_CFLAGS=$CFLAGS htop_save_LIBS=$LIBS @@ -1090,8 +1160,8 @@ if test "$with_libunwind" != no; then if test "$with_libunwind" = yes; then AC_MSG_WARN([this build of libunwind might not support local unwinding]) else - LIBS=$htop_save_LIBS with_libunwind=no + have_libunwind_ptrace=no fi fi ], @@ -1099,22 +1169,39 @@ if test "$with_libunwind" != no; then AC_MSG_RESULT([no]) if test "$with_libunwind" = yes; then AC_MSG_FAILURE([cannot link with libunwind]) - else - LIBS=$htop_save_LIBS - with_libunwind=no fi + with_libunwind=no + have_libunwind_ptrace=no ] ) + if test "$have_libunwind_ptrace" != no && test "x$ac_cv_lib_unwind_ptrace__UPT_create" != xyes; then + dnl Expect LIBS to contain "-lunwind-ptrace" at this point. + dnl Therefore call AC_CHECK_FUNC and not AC_CHECK_LIB. + AC_CHECK_FUNC([_UPT_create], [], [have_libunwind_ptrace=no]) + fi + CPPFLAGS=$htop_save_CPPFLAGS CFLAGS=$htop_save_CFLAGS + LIBS=$htop_save_LIBS fi if test "$with_libunwind" != no; then AM_CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS" + LIBS="$LIBUNWIND_LIBS $LIBS" with_libunwind=yes AC_DEFINE([HAVE_LOCAL_UNWIND], 1, [Define to 1 if local unwinding is enabled.]) + if test "$have_libunwind_ptrace" != no; then + have_libunwind_ptrace=yes + backtrace_backend_found=yes + enable_backtrace=unwind-ptrace + AC_DEFINE([HAVE_LIBUNWIND_PTRACE], 1, [Define to 1 if libunwind-ptrace is present]) + fi else + if test "x$enable_backtrace" = xunwind-ptrace; then + AC_MSG_ERROR([requires --with-libunwind for --enable-backtrace=unwind-ptrace]) + fi + # Fall back to backtrace(3) and add -lexecinfo if needed AC_SEARCH_LIBS([backtrace], [execinfo]) @@ -1180,6 +1267,21 @@ char** (*fn)(void* const*, BACKTRACE_RETURN_TYPE) = backtrace_symbols; ) fi +case $enable_backtrace in + no|check) + enable_backtrace=no + ;; + yes) + AC_MSG_ERROR([did not find any backend for --enable-backtrace]) + ;; + *) + if test "$backtrace_backend_found" = no; then + AC_MSG_ERROR([backend '$enable_backtrace' for --enable-backtrace is not supported or not found]) + fi + AC_DEFINE([HAVE_BACKTRACE_SCREEN], [1], [Define if the backtrace feature is enabled]) + ;; +esac +AM_CONDITIONAL([HAVE_BACKTRACE_SCREEN], [test "$enable_backtrace" != no]) AC_ARG_ENABLE( [hwloc], @@ -1616,6 +1718,7 @@ AC_MSG_RESULT([ (Linux) capabilities: $enable_capabilities unicode: $enable_unicode affinity: $enable_affinity + backtrace: $enable_backtrace hwloc: $enable_hwloc debug: $enable_debug static: $enable_static From e6d2b911490a977c4e37f83762752e77bd4d7405 Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Fri, 20 Feb 2026 21:59:26 +0800 Subject: [PATCH 4/9] Backtrace screen feature (using libunwind-ptrace) Add a new feature to display backtrace of any process. The new backtrace screen can be started by pressing the key 'b' when a process is selected. This commit contains only the very basic information of the backtrace screen (frame indices, addresses and symbol names). Additional information like library names and demangling support will be added in subsequent commits. The code in this commit was primarily written by Odric. Kang-Che and Benny provided code improvements and bug fixes that were incorporated into this main commit. Co-authored-by: Odric Roux-Paris Co-authored-by: Kang-Che Sung Co-authored-by: Benny Baumann --- Action.c | 41 +++++ BacktraceScreen.c | 354 +++++++++++++++++++++++++++++++++++++++++ BacktraceScreen.h | 76 +++++++++ Makefile.am | 10 ++ README.md | 5 + generic/UnwindPtrace.c | 131 +++++++++++++++ generic/UnwindPtrace.h | 19 +++ htop.1.in | 4 + 8 files changed, 640 insertions(+) create mode 100644 BacktraceScreen.c create mode 100644 BacktraceScreen.h create mode 100644 generic/UnwindPtrace.c create mode 100644 generic/UnwindPtrace.h diff --git a/Action.c b/Action.c index 8123c4c8d..a6094ce9c 100644 --- a/Action.c +++ b/Action.c @@ -28,7 +28,9 @@ in the source distribution for its full text. #include "Macros.h" #include "MainPanel.h" #include "MemoryMeter.h" +#include "Object.h" #include "OpenFilesScreen.h" +#include "Panel.h" #include "Platform.h" #include "Process.h" #include "ProcessLocksScreen.h" @@ -49,6 +51,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; @@ -606,6 +612,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; @@ -689,6 +724,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" }, @@ -941,6 +979,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..4686e9738 --- /dev/null +++ b/BacktraceScreen.c @@ -0,0 +1,354 @@ +/* +htop - BacktraceScreen.c +(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" + +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "Macros.h" +#include "Object.h" +#include "Panel.h" +#include "Process.h" +#include "ProvideCurses.h" +#include "RichString.h" +#include "Settings.h" +#include "Vector.h" +#include "XUtils.h" +#include "generic/UnwindPtrace.h" + + +#if defined(HAVE_BACKTRACE_SCREEN) + +static const char* const BacktraceScreenFunctions[] = { + "Refresh", + "Done ", + NULL +}; + +static const char* const BacktraceScreenKeys[] = { + "F5", + "Esc", + NULL +}; + +static const int BacktraceScreenEvents[] = { + KEY_F(5), + 27, +}; + +BacktraceFrameData* BacktraceFrameData_new(void) { + BacktraceFrameData* this = AllocThis(BacktraceFrameData); + this->address = 0; + this->offset = 0; + this->functionName = NULL; + this->index = 0; + this->isSignalFrame = false; + return this; +} + +void BacktraceFrameData_delete(Object* object) { + BacktraceFrameData* this = (BacktraceFrameData*)object; + free(this->functionName); + free(this); +} + +static void BacktracePanel_displayHeader(BacktracePanel* this) { + const BacktracePanelPrintingHelper* printingHelper = &this->printingHelper; + + /* + * 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 - strlen("0x")); + + char* line = NULL; + xAsprintf(&line, "%*s %-*s %s", + (int)printingHelper->maxFrameNumLen, "#", + (int)(printingHelper->maxAddrLen + strlen("0x")), "ADDRESS", + "NAME" + ); + + Panel_setHeader((Panel*)this, line); + free(line); +} + +static void BacktracePanel_makePrintingHelper(const BacktracePanel* this, BacktracePanelPrintingHelper* printingHelper) { + Vector* lines = this->super.items; + unsigned int maxFrameNum = 0; + 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; + } + + maxFrameNum = MAXIMUM(row->data.frame->index, maxFrameNum); + + longestAddress = MAXIMUM(row->data.frame->address, longestAddress); + } + + printingHelper->maxFrameNumLen = MAXIMUM(countDigits(maxFrameNum, 10), printingHelper->maxFrameNumLen); + printingHelper->maxAddrLen = MAXIMUM(countDigits(longestAddress, 16), printingHelper->maxAddrLen); +} + +static void BacktracePanel_makeBacktrace(Vector* frames, pid_t pid, char** error) { +#ifdef HAVE_LIBUNWIND_PTRACE + UnwindPtrace_makeBacktrace(frames, pid, error); +#else + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +#endif +} + +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); + BacktracePanel_makeBacktrace(data, Process_getPid(process), &error); + + BacktracePanelRow* header = BacktracePanelRow_new(this); + header->process = process; + header->type = BACKTRACE_PANEL_ROW_PROCESS_INFORMATION; + Panel_add((Panel*)this, (Object*)header); + + if (!error) { + for (int j = 0; j < Vector_size(data); j++) { + BacktracePanelRow* row = BacktracePanelRow_new(this); + row->process = process; + row->type = BACKTRACE_PANEL_ROW_DATA_FRAME; + row->data.frame = (BacktraceFrameData*)Vector_get(data, j); + + Panel_add((Panel*)this, (Object*)row); + } + } else { + BacktracePanelRow* errorRow = BacktracePanelRow_new(this); + errorRow->process = process; + errorRow->type = BACKTRACE_PANEL_ROW_ERROR; + errorRow->data.error = error; + error = NULL; + Panel_add((Panel*)this, (Object*)errorRow); + } + + 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; + + HandlerResult result = IGNORED; + + switch (ch) { + case KEY_CTRL('L'): + case KEY_F(5): + Panel_prune(super); + BacktracePanel_populateFrames(this); + break; + } + + return result; +} + +BacktracePanel* BacktracePanel_new(Vector* processes, const Settings* settings) { + BacktracePanel* this = AllocThis(BacktracePanel); + this->processes = processes; + + this->printingHelper.maxAddrLen = strlen("ADDRESS") - strlen("0x"); + this->printingHelper.maxFrameNumLen = strlen("#"); + + 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); + + return this; +} + +void BacktracePanel_delete(Object* object) { + BacktracePanel* this = (BacktracePanel*)object; + Vector_delete(this->processes); + Panel_delete(object); +} + +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; + + const char* processName = ""; + if (process->mergedCommand.str) { + processName = process->mergedCommand.str; + 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; + } + } + } else if (process->cmdline) { + processName = process->cmdline; + } + if (highlightLen == 0) { + highlightLen = strlen(processName); + } + + if (Process_isThread(process)) { + colorBasename = PROCESS_THREAD_BASENAME; + len = xAsprintf(&informations, "Thread %d: %n%s", Process_getPid(process), &indexProcessComm, processName); + } else { + colorBasename = PROCESS_BASENAME; + len = xAsprintf(&informations, "Process %d: %n%s",Process_getPid(process), &indexProcessComm, processName); + } + + RichString_appendnWide(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 BacktracePanel* panel = row->panel; + const BacktracePanelPrintingHelper* printingHelper = &panel->printingHelper; + + const BacktraceFrameData* frame = row->data.frame; + + const char* functionName = frame->functionName ? frame->functionName : "???"; + + char* completeFunctionName = NULL; + xAsprintf(&completeFunctionName, "%s+0x%zx", functionName, frame->offset); + + size_t maxAddrLen = printingHelper->maxAddrLen; + char* line = NULL; + + /* + * 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); + + int len = xAsprintf(&line, "%*u 0x%0*zx %s", + (int)printingHelper->maxFrameNumLen, frame->index, + (int)maxAddrLen, frame->address, + completeFunctionName + ); + + int colors = frame->functionName ? CRT_colors[DEFAULT_COLOR] : CRT_colors[PROCESS_SHADOW]; + + RichString_appendnAscii(out, colors, line, len); + + 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->panel = panel; + 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..f4dd7f026 --- /dev/null +++ b/BacktraceScreen.h @@ -0,0 +1,76 @@ +#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; + + size_t address; + size_t offset; + char* functionName; + unsigned int index; + bool isSignalFrame; +} BacktraceFrameData; + +typedef struct BacktracePanelPrintingHelper_ { + size_t maxAddrLen; + size_t maxFrameNumLen; +} BacktracePanelPrintingHelper; + +typedef struct BacktracePanel_ { + Panel super; + + Vector* processes; + BacktracePanelPrintingHelper printingHelper; + 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 BacktracePanel* panel; + const Process* process; +} 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 8c6b845c5..6d67b2384 100644 --- a/Makefile.am +++ b/Makefile.am @@ -163,6 +163,16 @@ myhtopheaders = \ Vector.h \ XUtils.h +if HAVE_BACKTRACE_SCREEN +myhtopheaders += \ + BacktraceScreen.h \ + generic/UnwindPtrace.h + +myhtopsources += \ + BacktraceScreen.c \ + generic/UnwindPtrace.c +endif + # Linux # ----- diff --git a/README.md b/README.md index 1194c0157..297113d32 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ To install on the local system run `make install`. By default `make install` ins enable hwloc support for CPU affinity; disables affinity support - dependency: *libhwloc* - default: *no* + * `--enable-backtrace`: + enable showing backtraces of a process + - default: *no* + - possible values: + - unwind-ptrace: use **libunwind-ptrace** to get backtraces * `--enable-static`: build a static htop binary; hwloc and delay accounting are not supported - default: *no* diff --git a/generic/UnwindPtrace.c b/generic/UnwindPtrace.c new file mode 100644 index 000000000..117063be0 --- /dev/null +++ b/generic/UnwindPtrace.c @@ -0,0 +1,131 @@ +/* +htop - generic/UnwindPtrace.c +(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 "generic/UnwindPtrace.h" + +#include +#include +#include +#include +#include + +#include "BacktraceScreen.h" +#include "XUtils.h" + +#ifdef HAVE_LIBUNWIND_PTRACE +#include +#include +#endif + + +#ifdef HAVE_LIBUNWIND_PTRACE +static int ptraceAttach(pid_t pid) { +# if defined(HTOP_LINUX) + return !ptrace(PTRACE_ATTACH, pid, (void*)0, (void*)0) ? 0 : errno; +# elif defined(HTOP_FREEBSD) || defined(HTOP_NETBSD) || defined(HTOP_OPENBSD) || defined(HTOP_DARWIN) + return !ptrace(PT_ATTACH, pid, (caddr_t)0, 0) ? 0 : errno; +# else + (void)pid; + return ENOSYS; +# endif +} + +static int ptraceDetach(pid_t pid) { +# if defined(HTOP_LINUX) + return !ptrace(PTRACE_DETACH, pid, (void*)0, (void*)0) ? 0 : errno; +# elif defined(HTOP_FREEBSD) || defined(HTOP_NETBSD) || defined(HTOP_OPENBSD) || defined(HTOP_DARWIN) + return !ptrace(PT_DETACH, pid, (caddr_t)0, 0) ? 0 : errno; +# else + (void)pid; + return ENOSYS; +# endif +} + +void UnwindPtrace_makeBacktrace(Vector* frames, pid_t pid, char** error) { + *error = NULL; + + if (pid <= 0) { + *error = xStrdup("Invalid PID"); + return; + } + + unw_addr_space_t addrSpace = unw_create_addr_space(&_UPT_accessors, 0); + if (!addrSpace) { + *error = xStrdup("Cannot initialize libunwind"); + return; + } + + int ptraceErrno = ptraceAttach(pid); + if (ptraceErrno) { + 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) { + *error = xStrdup("Cannot 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; + } + + unsigned int index = 0; + do { + char buffer[2048] = {0}; + + BacktraceFrameData* frame = BacktraceFrameData_new(); + frame->index = index; + + unw_word_t pc; + ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (ret != 0) { + xAsprintf(error, "Cannot get program counter register: error %d", -ret); + BacktraceFrameData_delete((Object *)frame); + break; + } + frame->address = pc; + + frame->isSignalFrame = unw_is_signal_frame(&cursor) > 0; + + unw_word_t offset; + if (unw_get_proc_name(&cursor, buffer, sizeof(buffer), &offset) == 0) { + frame->offset = offset; + frame->functionName = xStrndup(buffer, sizeof(buffer)); + } + Vector_add(frames, (Object *)frame); + index++; + } while (unw_step(&cursor) > 0 && index < INT_MAX); + +context_error: + _UPT_destroy(context); + +ptrace_error: + ptraceDetach(pid); + +addr_space_error: + unw_destroy_addr_space(addrSpace); +} +#endif diff --git a/generic/UnwindPtrace.h b/generic/UnwindPtrace.h new file mode 100644 index 000000000..dcfb42ba9 --- /dev/null +++ b/generic/UnwindPtrace.h @@ -0,0 +1,19 @@ +#ifndef HEADER_UnwindPtrace +#define HEADER_UnwindPtrace +/* +htop - generic/UnwindPtrace.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 "Vector.h" + + +#ifdef HAVE_LIBUNWIND_PTRACE +void UnwindPtrace_makeBacktrace(Vector* frames, pid_t pid, char** error); +#endif + +#endif diff --git a/htop.1.in b/htop.1.in index e812fe032..09aba1f81 100644 --- a/htop.1.in +++ b/htop.1.in @@ -279,6 +279,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 compile time.) +.TP .B Ctrl-L Refresh: redraw screen and recalculate values. .TP From 1f7a871ae0d969c4105b98420fd9a4e4af44aed5 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Sun, 1 Mar 2026 02:42:05 +0800 Subject: [PATCH 5/9] CI: Add backtrace screen to test builds This covers Linux and FreeBSD build jobs Co-authored-by: Odric Roux-Paris Co-authored-by: Kang-Che Sung --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b38a8389..598d70a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,15 +60,15 @@ jobs: steps: - uses: actions/checkout@v6 - 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 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 || ( 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' 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}-22 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-22 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-22 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-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 || ( 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' 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}-22 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-22 clang-tools-22 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev + run: sudo apt-get install --no-install-recommends clang-22 clang-tools-22 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libunwind-dev - name: Bootstrap run: ./autogen.sh - name: Configure - run: scan-build-22 -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-22 -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 || ( cat config.log; exit 1; ) - name: Build run: scan-build-22 -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-22 libclang-rt-22-dev llvm-22 - 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 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 || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Run sanitized htop (1) @@ -273,12 +273,12 @@ jobs: release: '15.0' usesh: true prepare: | - pkg install -y gmake autoconf automake pkgconf git + pkg install -y gmake autoconf automake pkgconf git libunwind git config --global --add safe.directory /home/runner/work/htop/htop run: | set -e ./autogen.sh - ./configure --enable-unicode --enable-werror + ./configure --enable-unicode --enable-werror --enable-backtrace gmake -k build-netbsd-latest-gcc: From a8ec22cef62e19e328082e921f2907e92114853d Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Sun, 1 Mar 2026 02:42:07 +0800 Subject: [PATCH 6/9] Show library filenames in BacktraceScreen Extend the backtrace screen feature to show library filenames as a new column in the backtrace screen. This feature depend on libunwind 1.8 or later (unw_get_elf_filename()) Co-authored-by: Benny Baumann Co-authored-by: Kang-Che Sung --- BacktraceScreen.c | 94 +++++++++++++++++++++++++++++++++++++++++- BacktraceScreen.h | 4 ++ configure.ac | 21 ++++++++++ generic/UnwindPtrace.c | 6 +++ 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/BacktraceScreen.c b/BacktraceScreen.c index 4686e9738..2bfe9e8cc 100644 --- a/BacktraceScreen.c +++ b/BacktraceScreen.c @@ -34,27 +34,35 @@ in the source distribution for its full text. #if defined(HAVE_BACKTRACE_SCREEN) static const char* const BacktraceScreenFunctions[] = { + "Full Path", "Refresh", "Done ", NULL }; static const char* const BacktraceScreenKeys[] = { + "F3", "F5", "Esc", NULL }; static const int BacktraceScreenEvents[] = { + KEY_F(3), KEY_F(5), 27, }; +typedef enum BacktraceScreenDisplayOptions_ { + SHOW_FULL_PATH_OBJECT = 1 << 0, +} BacktraceScreenDisplayOptions; + BacktraceFrameData* BacktraceFrameData_new(void) { BacktraceFrameData* this = AllocThis(BacktraceFrameData); this->address = 0; this->offset = 0; this->functionName = NULL; + this->objectPath = NULL; this->index = 0; this->isSignalFrame = false; return this; @@ -63,11 +71,16 @@ BacktraceFrameData* BacktraceFrameData_new(void) { void BacktraceFrameData_delete(Object* object) { BacktraceFrameData* this = (BacktraceFrameData*)object; free(this->functionName); + free(this->objectPath); free(this); } static void BacktracePanel_displayHeader(BacktracePanel* this) { const BacktracePanelPrintingHelper* printingHelper = &this->printingHelper; + const int displayOptions = this->displayOptions; + + bool showFullPathObject = !!(displayOptions & SHOW_FULL_PATH_OBJECT); + size_t maxObjLen = showFullPathObject ? printingHelper->maxObjPathLen : printingHelper->maxObjNameLen; /* * The parameters for printf are of type int. @@ -75,11 +88,13 @@ static void BacktracePanel_displayHeader(BacktracePanel* this) { */ assert(printingHelper->maxFrameNumLen <= INT_MAX); assert(printingHelper->maxAddrLen <= INT_MAX - strlen("0x")); + assert(maxObjLen <= INT_MAX); char* line = NULL; - xAsprintf(&line, "%*s %-*s %s", + xAsprintf(&line, "%*s %-*s %-*s %s", (int)printingHelper->maxFrameNumLen, "#", (int)(printingHelper->maxAddrLen + strlen("0x")), "ADDRESS", + (int)maxObjLen, "FILE", "NAME" ); @@ -87,6 +102,11 @@ static void BacktracePanel_displayHeader(BacktracePanel* this) { free(line); } +static const char* getBasename(const char* path) { + char *lastSlash = strrchr(path, '/'); + return lastSlash ? lastSlash + 1 : path; +} + static void BacktracePanel_makePrintingHelper(const BacktracePanel* this, BacktracePanelPrintingHelper* printingHelper) { Vector* lines = this->super.items; unsigned int maxFrameNum = 0; @@ -98,6 +118,15 @@ static void BacktracePanel_makePrintingHelper(const BacktracePanel* this, Backtr continue; } + if (row->data.frame->objectPath) { + const char* objectName = getBasename(row->data.frame->objectPath); + size_t objectNameLength = strlen(objectName); + size_t objectPathLength = (size_t)(objectName - row->data.frame->objectPath) + objectNameLength; + + printingHelper->maxObjNameLen = MAXIMUM(objectNameLength, printingHelper->maxObjNameLen); + printingHelper->maxObjPathLen = MAXIMUM(objectPathLength, printingHelper->maxObjPathLen); + } + maxFrameNum = MAXIMUM(row->data.frame->index, maxFrameNum); longestAddress = MAXIMUM(row->data.frame->address, longestAddress); @@ -159,10 +188,22 @@ static void BacktracePanel_populateFrames(BacktracePanel* this) { static HandlerResult BacktracePanel_eventHandler(Panel* super, int ch) { BacktracePanel* this = (BacktracePanel*)super; + int* const displayOptions = &this->displayOptions; HandlerResult result = IGNORED; switch (ch) { + case 'p': + case KEY_F(3): + *displayOptions ^= SHOW_FULL_PATH_OBJECT; + + bool showFullPathObject = !!(*displayOptions & SHOW_FULL_PATH_OBJECT); + FunctionBar_setLabel(super->defaultBar, KEY_F(3), showFullPathObject ? "Basename " : "Full Path"); + + this->super.needsRedraw = true; + BacktracePanel_displayHeader(this); + break; + case KEY_CTRL('L'): case KEY_F(5): Panel_prune(super); @@ -179,14 +220,22 @@ BacktracePanel* BacktracePanel_new(Vector* processes, const Settings* settings) this->printingHelper.maxAddrLen = strlen("ADDRESS") - strlen("0x"); this->printingHelper.maxFrameNumLen = strlen("#"); + this->printingHelper.maxObjNameLen = strlen("FILE"); + this->printingHelper.maxObjPathLen = strlen("FILE"); this->settings = settings; + this->displayOptions = + (settings->showProgramPath ? SHOW_FULL_PATH_OBJECT : 0) | + 0; Panel* super = (Panel*) this; Panel_init(super, 1, 1, 0, 1, Class(BacktracePanelRow), true, FunctionBar_new(BacktraceScreenFunctions, BacktraceScreenKeys, BacktraceScreenEvents) ); + bool showFullPathObject = !!(this->displayOptions & SHOW_FULL_PATH_OBJECT); + FunctionBar_setLabel(super->defaultBar, KEY_F(3), showFullPathObject ? "Basename " : "Full Path"); + BacktracePanel_populateFrames(this); return this; @@ -198,6 +247,31 @@ void BacktracePanel_delete(Object* object) { 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] != 0 && 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); @@ -253,6 +327,7 @@ static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) const BacktracePanel* panel = row->panel; const BacktracePanelPrintingHelper* printingHelper = &panel->printingHelper; + const int displayOptions = panel->displayOptions; const BacktraceFrameData* frame = row->data.frame; @@ -261,8 +336,17 @@ static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) char* completeFunctionName = NULL; xAsprintf(&completeFunctionName, "%s+0x%zx", functionName, frame->offset); + size_t objectLength = printingHelper->maxObjPathLen; + const char* objectDisplayed = frame->objectPath; + if (!(displayOptions & SHOW_FULL_PATH_OBJECT)) { + objectLength = printingHelper->maxObjNameLen; + if (frame->objectPath) + objectDisplayed = getBasename(frame->objectPath); + } + size_t maxAddrLen = printingHelper->maxAddrLen; char* line = NULL; + int objectPathStart = -1; /* * The parameters for printf are of type int. @@ -270,10 +354,13 @@ static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) */ assert(printingHelper->maxFrameNumLen <= INT_MAX); assert(maxAddrLen <= INT_MAX); + assert(objectLength <= INT_MAX); - int len = xAsprintf(&line, "%*u 0x%0*zx %s", + int len = xAsprintf(&line, "%*u 0x%0*zx %n%-*s %s", (int)printingHelper->maxFrameNumLen, frame->index, (int)maxAddrLen, frame->address, + &objectPathStart, + (int)objectLength, objectDisplayed ? objectDisplayed : "-", completeFunctionName ); @@ -281,6 +368,9 @@ static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) RichString_appendnAscii(out, colors, line, len); + if (panel->settings->highlightBaseName) + BacktracePanelRow_highlightBasename(row, out, line, objectPathStart); + free(completeFunctionName); free(line); } diff --git a/BacktraceScreen.h b/BacktraceScreen.h index f4dd7f026..c603c2a49 100644 --- a/BacktraceScreen.h +++ b/BacktraceScreen.h @@ -23,6 +23,7 @@ typedef struct BacktraceFrameData_ { size_t address; size_t offset; char* functionName; + char* objectPath; unsigned int index; bool isSignalFrame; } BacktraceFrameData; @@ -30,6 +31,8 @@ typedef struct BacktraceFrameData_ { typedef struct BacktracePanelPrintingHelper_ { size_t maxAddrLen; size_t maxFrameNumLen; + size_t maxObjPathLen; + size_t maxObjNameLen; } BacktracePanelPrintingHelper; typedef struct BacktracePanel_ { @@ -38,6 +41,7 @@ typedef struct BacktracePanel_ { Vector* processes; BacktracePanelPrintingHelper printingHelper; const Settings* settings; + int displayOptions; } BacktracePanel; typedef enum BacktracePanelRowType_ { diff --git a/configure.ac b/configure.ac index 9c540553d..bcabd11f8 100644 --- a/configure.ac +++ b/configure.ac @@ -1175,6 +1175,27 @@ if test "$with_libunwind" != no; then ] ) + AC_MSG_CHECKING([for unw_get_elf_filename in $LIBUNWIND_LIBS]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ +#define UNW_LOCAL_ONLY +#include + ]], [[ + static unw_cursor_t cursor; + /* Requires libunwind (HP implementation) 1.8 or later. + 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) + AC_MSG_WARN([showing filenames in backtrace screen requires libunwind 1.8 or later]) + ] + ) + if test "$have_libunwind_ptrace" != no && test "x$ac_cv_lib_unwind_ptrace__UPT_create" != xyes; then dnl Expect LIBS to contain "-lunwind-ptrace" at this point. dnl Therefore call AC_CHECK_FUNC and not AC_CHECK_LIB. diff --git a/generic/UnwindPtrace.c b/generic/UnwindPtrace.c index 117063be0..f2925c9d2 100644 --- a/generic/UnwindPtrace.c +++ b/generic/UnwindPtrace.c @@ -110,6 +110,12 @@ void UnwindPtrace_makeBacktrace(Vector* frames, pid_t pid, char** error) { frame->isSignalFrame = unw_is_signal_frame(&cursor) > 0; +# if defined(HAVE_LIBUNWIND_ELF_FILENAME) + unw_word_t offsetElfFileName; + if (unw_get_elf_filename(&cursor, buffer, sizeof(buffer), &offsetElfFileName) == 0) + frame->objectPath = xStrndup(buffer, sizeof(buffer)); +# endif + unw_word_t offset; if (unw_get_proc_name(&cursor, buffer, sizeof(buffer), &offset) == 0) { frame->offset = offset; From 2336509023d1d6368e66858fd6857919622d5e3c Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Sun, 1 Mar 2026 02:42:13 +0800 Subject: [PATCH 7/9] build: Add '--enable-demangling' configure option For demangling support in the backtrace screen, which will be added in the next commit. Two backends will be supported: libiberty (GNU) and libdemangle (Solaris). This commit adds checks for both of them. Co-authored-by: Odric Roux-Paris Co-authored-by: Benny Baumann Signed-off-by: Kang-Che Sung --- Makefile.am | 3 +- configure.ac | 134 ++++++++++++++++++++++++++++++++++++++ generic/ProvideDemangle.h | 35 ++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 generic/ProvideDemangle.h diff --git a/Makefile.am b/Makefile.am index 6d67b2384..db4fb7195 100644 --- a/Makefile.am +++ b/Makefile.am @@ -161,7 +161,8 @@ myhtopheaders = \ UptimeMeter.h \ UsersTable.h \ Vector.h \ - XUtils.h + XUtils.h \ + generic/ProvideDemangle.h if HAVE_BACKTRACE_SCREEN myhtopheaders += \ diff --git a/configure.ac b/configure.ac index bcabd11f8..7482aef1b 100644 --- a/configure.ac +++ b/configure.ac @@ -1304,6 +1304,139 @@ case $enable_backtrace in esac AM_CONDITIONAL([HAVE_BACKTRACE_SCREEN], [test "$enable_backtrace" != no]) +AC_ARG_ENABLE( + [demangling], + [AS_HELP_STRING( + [--enable-demangling=BACKEND], + [enable demangling support for backtraces; available backends: libiberty libdemangle @<:@default=check@:>@] + )], + [], + [enable_demangling=check] +) + +demangling_backend_found=no +have_libiberty=no +have_libdemangle=no +case $enable_demangling in + check|yes) + have_libiberty=check + have_libdemangle=check + ;; + libiberty|iberty|liberty) + have_libiberty=check + enable_demangling=libiberty + ;; + libdemangle) + have_libdemangle=check + ;; +esac + +demangle_header_paths='' + +if test "$have_libiberty" != no; then + AC_CHECK_LIB( + [iberty], + [cplus_demangle], + [demangle_header_paths="/usr/local/include/libiberty /usr/include/libiberty"], + [have_libiberty=no] + ) +fi +if test "$have_libdemangle" != no; then + AC_CHECK_LIB([demangle], [cplus_demangle], [:], [have_libdemangle=no]) +fi + +if test "$have_libiberty$have_libdemangle" != nono; then + if htop_search_header_dir demangle.h "$demangle_header_paths"; then + AC_DEFINE([HAVE_DEMANGLE_H], 1, [Define to 1 if you have the header file.]) + else + have_libdemangle=no + if test "$have_libiberty" != no; then + AC_CHECK_HEADERS([libiberty/demangle.h], [], [have_libiberty=no]) + fi + fi +fi + +htop_save_CPPFLAGS=$CPPFLAGS +htop_save_LIBS=$LIBS +CPPFLAGS="$AM_CPPFLAGS -I$srcdir $CPPFLAGS" + +if test "$demangling_backend_found" = no && test "$have_libiberty" != no; then + LIBS="-liberty $htop_save_LIBS" + + AC_MSG_CHECKING([whether libiberty cplus_demangle works]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include "generic/ProvideDemangle.h" +/* char* cplus_demangle(const char* mangled, int options); */ + ]], [[ + char* demangled = cplus_demangle("", DMGL_AUTO); + free(demangled); + ]] + )], [ + AC_MSG_RESULT([yes]) + have_libiberty=yes + demangling_backend_found=yes + enable_demangling=libiberty + AC_DEFINE([HAVE_LIBIBERTY_CPLUS_DEMANGLE], [1], [Define to 1 if libiberty supports cplus_demangle function.]) + ], [ + AC_MSG_RESULT([no]) + have_libiberty=no + LIBS=$htop_save_LIBS + ] + ) +fi + +if test "$demangling_backend_found" = no && test "$have_libdemangle" != no; then + LIBS="-ldemangle $htop_save_LIBS" + + AC_MSG_CHECKING([whether libdemangle cplus_demangle works]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include "generic/ProvideDemangle.h" +/* int cplus_demangle(const char* symbol, char* interpretation, size_t size); */ + ]], [[ + static char demangled[1]; + int ret = cplus_demangle("", demangled, sizeof(demangled)); + if (ret == DEMANGLE_ENAME) { + ret = 0; + } + return (ret <= 0 && ret > -126 ? ret : 1); + ]] + )], [ + AC_MSG_RESULT([yes]) + have_libdemangle=yes + demangling_backend_found=yes + enable_demangling=libdemangle + AC_DEFINE([HAVE_LIBDEMANGLE_CPLUS_DEMANGLE], [1], [Define to 1 if libdemangle supports cplus_demangle function.]) + ], [ + AC_MSG_RESULT([no]) + have_libdemangle=no + LIBS=$htop_save_LIBS + ] + ) +fi + +CPPFLAGS=$htop_save_CPPFLAGS + +case $enable_demangling in + no|check) + enable_demangling=no + ;; + yes) + AC_MSG_ERROR([did not find any backend for --enable-demangling]) + ;; + *) + if test "$demangling_backend_found" = no; then + AC_MSG_ERROR([backend '$enable_demangling' for --enable-demangling is not supported or not found]) + fi + AC_DEFINE([HAVE_DEMANGLING], [1], [Define to 1 if the demangling is supported.]) + ;; +esac +AM_CONDITIONAL([HAVE_DEMANGLING], [test "$have_demangling" != no]) + AC_ARG_ENABLE( [hwloc], [AS_HELP_STRING( @@ -1740,6 +1873,7 @@ AC_MSG_RESULT([ unicode: $enable_unicode affinity: $enable_affinity backtrace: $enable_backtrace + demangling: $enable_demangling hwloc: $enable_hwloc debug: $enable_debug static: $enable_static diff --git a/generic/ProvideDemangle.h b/generic/ProvideDemangle.h new file mode 100644 index 000000000..926a2fdf1 --- /dev/null +++ b/generic/ProvideDemangle.h @@ -0,0 +1,35 @@ +#ifndef HEADER_ProvideDemangle +#define HEADER_ProvideDemangle +/* +htop - generic/ProvideDemangle.h +(C) 2026 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +// IWYU pragma: no_include "config.h" + +// IWYU pragma: begin_exports + +#if !defined(HAVE_DECL_BASENAME) +// Suppress libiberty's own declaration of basename(3) +// +// It's a pity that we need this workaround as libiberty developers +// refuse fix their headers and export an unwanted interface to us. +// htop doesn't use basename(3) API: The POSIX version is flawed by +// design; libiberty ships with the GNU version of basename(3) that +// is incompatible with the interface specified by POSIX). +// +// +#define HAVE_DECL_BASENAME 1 +#endif + +#if defined(HAVE_DEMANGLE_H) +#include +#elif defined(HAVE_LIBIBERTY_DEMANGLE_H) +#include +#endif + +// IWYU pragma: end_exports + +#endif /* HEADER_ProvideDemangle */ From 0ae08843e8c55174cc99d2f57e4d88e3c151f07c Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Wed, 25 Mar 2026 12:16:48 +0800 Subject: [PATCH 8/9] Demangling support in BacktraceScreen Add support of demangling symbol names in the backtrace screen. Two backends are supported: libiberty (GNU) and libdemangle (Solaris). [The initial code for demangling support with libiberty was written by Odric. Kang-Che expanded the code to support libdemangle. Benny provided code reviewes and minor fixes.] Co-authored-by: Kang-Che Sung Co-authored-by: Benny Baumann --- BacktraceScreen.c | 44 +++++++++++++++++++++++++-- BacktraceScreen.h | 2 ++ Makefile.am | 5 ++++ README.md | 6 ++++ generic/Demangle.c | 67 ++++++++++++++++++++++++++++++++++++++++++ generic/Demangle.h | 24 +++++++++++++++ generic/UnwindPtrace.c | 9 ++++++ 7 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 generic/Demangle.c create mode 100644 generic/Demangle.h diff --git a/BacktraceScreen.c b/BacktraceScreen.c index 2bfe9e8cc..e75c66a64 100644 --- a/BacktraceScreen.c +++ b/BacktraceScreen.c @@ -34,6 +34,9 @@ in the source distribution for its full text. #if defined(HAVE_BACKTRACE_SCREEN) static const char* const BacktraceScreenFunctions[] = { +#if defined(HAVE_DEMANGLING) + "Mangle ", +#endif "Full Path", "Refresh", "Done ", @@ -41,6 +44,9 @@ static const char* const BacktraceScreenFunctions[] = { }; static const char* const BacktraceScreenKeys[] = { +#if defined(HAVE_DEMANGLING) + "F2", +#endif "F3", "F5", "Esc", @@ -48,13 +54,17 @@ static const char* const BacktraceScreenKeys[] = { }; static const int BacktraceScreenEvents[] = { +#if defined(HAVE_DEMANGLING) + KEY_F(2), +#endif KEY_F(3), KEY_F(5), 27, }; typedef enum BacktraceScreenDisplayOptions_ { - SHOW_FULL_PATH_OBJECT = 1 << 0, + DEMANGLE_NAME_FUNCTION = 1 << 0, + SHOW_FULL_PATH_OBJECT = 1 << 1, } BacktraceScreenDisplayOptions; BacktraceFrameData* BacktraceFrameData_new(void) { @@ -62,6 +72,7 @@ BacktraceFrameData* BacktraceFrameData_new(void) { this->address = 0; this->offset = 0; this->functionName = NULL; + this->demangleFunctionName = NULL; this->objectPath = NULL; this->index = 0; this->isSignalFrame = false; @@ -71,6 +82,7 @@ BacktraceFrameData* BacktraceFrameData_new(void) { void BacktraceFrameData_delete(Object* object) { BacktraceFrameData* this = (BacktraceFrameData*)object; free(this->functionName); + free(this->demangleFunctionName); free(this->objectPath); free(this); } @@ -79,6 +91,8 @@ static void BacktracePanel_displayHeader(BacktracePanel* this) { const BacktracePanelPrintingHelper* printingHelper = &this->printingHelper; const int displayOptions = this->displayOptions; + bool showDemangledNames = (displayOptions & DEMANGLE_NAME_FUNCTION) && printingHelper->hasDemangledNames; + bool showFullPathObject = !!(displayOptions & SHOW_FULL_PATH_OBJECT); size_t maxObjLen = showFullPathObject ? printingHelper->maxObjPathLen : printingHelper->maxObjNameLen; @@ -95,7 +109,7 @@ static void BacktracePanel_displayHeader(BacktracePanel* this) { (int)printingHelper->maxFrameNumLen, "#", (int)(printingHelper->maxAddrLen + strlen("0x")), "ADDRESS", (int)maxObjLen, "FILE", - "NAME" + (showDemangledNames ? "NAME (demangled)" : "NAME") ); Panel_setHeader((Panel*)this, line); @@ -112,12 +126,17 @@ static void BacktracePanel_makePrintingHelper(const BacktracePanel* this, Backtr unsigned int maxFrameNum = 0; size_t longestAddress = 0; + printingHelper->hasDemangledNames = false; + 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; } + if (row->data.frame->demangleFunctionName) + printingHelper->hasDemangledNames = true; + if (row->data.frame->objectPath) { const char* objectName = getBasename(row->data.frame->objectPath); size_t objectNameLength = strlen(objectName); @@ -193,6 +212,18 @@ static HandlerResult BacktracePanel_eventHandler(Panel* super, int ch) { HandlerResult result = IGNORED; switch (ch) { +#if defined(HAVE_DEMANGLING) + case KEY_F(2): + *displayOptions ^= DEMANGLE_NAME_FUNCTION; + + bool showDemangledNames = !!(*displayOptions & DEMANGLE_NAME_FUNCTION); + FunctionBar_setLabel(super->defaultBar, KEY_F(2), showDemangledNames ? "Mangle " : "Demangle"); + + this->super.needsRedraw = true; + BacktracePanel_displayHeader(this); + break; +#endif + case 'p': case KEY_F(3): *displayOptions ^= SHOW_FULL_PATH_OBJECT; @@ -222,9 +253,11 @@ BacktracePanel* BacktracePanel_new(Vector* processes, const Settings* settings) this->printingHelper.maxFrameNumLen = strlen("#"); this->printingHelper.maxObjNameLen = strlen("FILE"); this->printingHelper.maxObjPathLen = strlen("FILE"); + this->printingHelper.hasDemangledNames = false; this->settings = settings; this->displayOptions = + DEMANGLE_NAME_FUNCTION | (settings->showProgramPath ? SHOW_FULL_PATH_OBJECT : 0) | 0; @@ -331,7 +364,12 @@ static void BacktracePanelRow_displayFrame(const Object* super, RichString* out) const BacktraceFrameData* frame = row->data.frame; - const char* functionName = frame->functionName ? frame->functionName : "???"; + const char* functionName = "???"; + if ((displayOptions & DEMANGLE_NAME_FUNCTION) && frame->demangleFunctionName) { + functionName = frame->demangleFunctionName; + } else if (frame->functionName) { + functionName = frame->functionName; + } char* completeFunctionName = NULL; xAsprintf(&completeFunctionName, "%s+0x%zx", functionName, frame->offset); diff --git a/BacktraceScreen.h b/BacktraceScreen.h index c603c2a49..8ee68e146 100644 --- a/BacktraceScreen.h +++ b/BacktraceScreen.h @@ -23,6 +23,7 @@ typedef struct BacktraceFrameData_ { size_t address; size_t offset; char* functionName; + char* demangleFunctionName; char* objectPath; unsigned int index; bool isSignalFrame; @@ -33,6 +34,7 @@ typedef struct BacktracePanelPrintingHelper_ { size_t maxFrameNumLen; size_t maxObjPathLen; size_t maxObjNameLen; + bool hasDemangledNames; } BacktracePanelPrintingHelper; typedef struct BacktracePanel_ { diff --git a/Makefile.am b/Makefile.am index db4fb7195..ef1c44533 100644 --- a/Makefile.am +++ b/Makefile.am @@ -172,6 +172,11 @@ myhtopheaders += \ myhtopsources += \ BacktraceScreen.c \ generic/UnwindPtrace.c + +if HAVE_DEMANGLING +myhtopheaders += generic/Demangle.h +myhtopsources += generic/Demangle.c +endif endif # Linux diff --git a/README.md b/README.md index 297113d32..8627436df 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ To install on the local system run `make install`. By default `make install` ins - default: *no* - possible values: - unwind-ptrace: use **libunwind-ptrace** to get backtraces + * `--enable-demangling`: + enable demangling support for backtraces + - default: *check* + - possible values: + - libiberty: use **libiberty** (GNU) to demangle function names + - libdemangle: use **libdemangle** (Solaris) to demangle function names * `--enable-static`: build a static htop binary; hwloc and delay accounting are not supported - default: *no* diff --git a/generic/Demangle.c b/generic/Demangle.c new file mode 100644 index 000000000..fc5a0420b --- /dev/null +++ b/generic/Demangle.c @@ -0,0 +1,67 @@ +/* +htop - generic/Demangle.c +(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 "generic/Demangle.h" + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include "generic/ProvideDemangle.h" + + +#ifdef HAVE_DEMANGLING +char* Demangle_demangle(const char* mangled) { +# if defined(HAVE_LIBIBERTY_CPLUS_DEMANGLE) + // The default in htop is to show as many details from the mangled + // name as possible. + int options = DMGL_AUTO | + DMGL_TYPES | + DMGL_VERBOSE /* Includes "disambiguators" of Rust crates */ | + DMGL_PARAMS; + + return cplus_demangle(mangled, options); +# elif defined(HAVE_LIBDEMANGLE_CPLUS_DEMANGLE) + // The cplus_demangle() API from libdemangle is flawed. It does not + // provide us the required size of the buffer to store the demangled + // name, and it leaves the buffer content undefined if the specified + // buffer size is not big enough. + + // No crash on allocation failure. This is for safety against + // incredibly long demangled names in untrustable programs. + static size_t bufSize = 128; + static char* buf; + + while (true) { + if (!buf) { + buf = malloc(bufSize); + if (!buf) + return NULL; + } + + int res = cplus_demangle(mangled, buf, bufSize); + if (res == 0) + break; + if (res != DEMANGLE_ESPACE || bufSize > SIZE_MAX / 2) + return NULL; + + bufSize *= 2; + free(buf); + buf = NULL; + } + + return strdup(buf); +# else + (void)mangled; + return NULL; +# endif +} +#endif /* HAVE_DEMANGLING */ diff --git a/generic/Demangle.h b/generic/Demangle.h new file mode 100644 index 000000000..0fa862ad0 --- /dev/null +++ b/generic/Demangle.h @@ -0,0 +1,24 @@ +#ifndef HEADER_Demangle +#define HEADER_Demangle +/* +htop - generic/Demangle.h +(C) 2025 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Macros.h" + + +#ifdef HAVE_DEMANGLING +ATTR_NONNULL ATTR_MALLOC +char* Demangle_demangle(const char* mangled); +#else +ATTR_NONNULL +static inline char* Demangle_demangle(const char* mangled) { + (void)mangled; + return NULL; +} +#endif /* HAVE_DEMANGLING */ + +#endif diff --git a/generic/UnwindPtrace.c b/generic/UnwindPtrace.c index f2925c9d2..7f93aaafa 100644 --- a/generic/UnwindPtrace.c +++ b/generic/UnwindPtrace.c @@ -23,6 +23,10 @@ in the source distribution for its full text. #include #endif +#ifdef HAVE_DEMANGLING +#include "generic/Demangle.h" +#endif + #ifdef HAVE_LIBUNWIND_PTRACE static int ptraceAttach(pid_t pid) { @@ -120,6 +124,11 @@ void UnwindPtrace_makeBacktrace(Vector* frames, pid_t pid, char** error) { if (unw_get_proc_name(&cursor, buffer, sizeof(buffer), &offset) == 0) { frame->offset = offset; frame->functionName = xStrndup(buffer, sizeof(buffer)); + +# if defined(HAVE_DEMANGLING) + char* demangledName = Demangle_demangle(frame->functionName); + frame->demangleFunctionName = demangledName; +# endif } Vector_add(frames, (Object *)frame); index++; From 349519fb79f8a57399be8ec8e13565a8598169c5 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Wed, 25 Mar 2026 12:17:29 +0800 Subject: [PATCH 9/9] CI: Add libiberty and demangling support to backtrace screen build For Linux and FreeBSD build jobs --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 598d70a84..7ae456796 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,15 +60,15 @@ jobs: steps: - uses: actions/checkout@v6 - 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 libunwind-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 --enable-backtrace || ( 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 || ( 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 --enable-backtrace' + 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' 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}-22 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-22 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libunwind-dev + run: sudo apt-get install --no-install-recommends clang-22 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 --enable-backtrace || ( 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 || ( 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 --enable-backtrace' + 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' 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}-22 main" -y sudo apt-get update -q - name: Install Dependencies - run: sudo apt-get install --no-install-recommends clang-22 clang-tools-22 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev libunwind-dev + run: sudo apt-get install --no-install-recommends clang-22 clang-tools-22 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-22 -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 || ( cat config.log; exit 1; ) + run: scan-build-22 -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 || ( cat config.log; exit 1; ) - name: Build run: scan-build-22 -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-22 libclang-rt-22-dev llvm-22 - 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 libunwind-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 --enable-backtrace || ( 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 || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Run sanitized htop (1) @@ -273,12 +273,12 @@ jobs: release: '15.0' usesh: true prepare: | - pkg install -y gmake autoconf automake pkgconf git libunwind + pkg install -y gmake autoconf automake pkgconf git libunwind gnulibiberty git config --global --add safe.directory /home/runner/work/htop/htop run: | set -e ./autogen.sh - ./configure --enable-unicode --enable-werror --enable-backtrace + ./configure --enable-unicode --enable-werror --enable-backtrace --enable-demangling gmake -k build-netbsd-latest-gcc: