diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 451ff0f8b..963cdeb61 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 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 || ( 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' 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 || ( 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' 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 || ( 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 || ( cat config.log; exit 1; ) - name: Build run: make -k - name: Run sanitized htop (1) @@ -273,12 +273,12 @@ jobs: release: '14.3' usesh: true prepare: | - pkg install -y gmake autoconf automake pkgconf git + 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 + ./configure --enable-unicode --enable-werror --enable-backtrace --enable-demangling gmake -k build-netbsd-latest-gcc: 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..7253364ef --- /dev/null +++ b/BacktraceScreen.c @@ -0,0 +1,537 @@ +/* +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" + +#include +#include +#include +#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) + +#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_makeBacktrace(Vector* frames, pid_t pid, char** error) { +#ifdef HAVE_LIBUNWIND_PTRACE + UnwindPtrace_makeBacktrace(frames, pid, error); + return; +#endif + + (void)frames; + (void)pid; + xAsprintf(error, "The backtrace screen is not implemented"); +} + +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); + + 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; + size_t 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..899e0bca5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,6 +166,21 @@ myhtopheaders = \ Vector.h \ XUtils.h +if HAVE_BACKTRACE_SCREEN +myhtopheaders += \ + BacktraceScreen.h \ + generic/UnwindPtrace.h + +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 f88b2c700..87a6acf0d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,17 @@ 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-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/configure.ac b/configure.ac index a5b49d9a1..55ae1eda7 100644 --- a/configure.ac +++ b/configure.ac @@ -981,47 +981,122 @@ $3 } # htop_search_header_dir AC_ARG_ENABLE( - [unwind], + [backtrace], [AS_HELP_STRING( - [--enable-unwind], - [enable unwind support for printing backtraces; requires libunwind @<:@default=check@:>@] + [--enable-backtrace=BACKEND], + [enable showing backtraces of a processes; available backends: unwind-ptrace @<:@default=no@:>@] )], [], - [enable_unwind=check] + [enable_backtrace=no] ) -case "$enable_unwind" in + +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( + [--with-libunwind], + [use libunwind for printing local backtrace @<:@default=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 + ] +) + +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" - if htop_search_header_dir libunwind.h "/usr/include/libunwind"; then + if htop_search_header_dir libunwind.h "/usr/local/include/libunwind /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 + 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([can not find required header file libunwind.h]) else - enable_unwind=no + with_libunwind=no + have_libunwind_ptrace=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 + 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 @@ -1029,8 +1104,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 + # 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( @@ -1071,34 +1148,69 @@ 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 + have_libunwind_ptrace=no fi fi ], [ AC_MSG_RESULT([no]) - if test "$enable_unwind" = yes; then + if test "$with_libunwind" = yes; then AC_MSG_FAILURE([can not link with libunwind]) - else - LIBS=$htop_save_LIBS - enable_unwind=no fi + with_libunwind=no + have_libunwind_ptrace=no ] ) + 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) + ) + + 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 "$enable_unwind" != no; then +if test "$with_libunwind" != no; then AM_CFLAGS="$AM_CFLAGS $LIBUNWIND_CFLAGS" - enable_unwind=yes + 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]) @@ -1164,6 +1276,152 @@ 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( + [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_libiberty=no + have_libdemangle=no + fi +fi + +htop_save_CPPFLAGS=$CPPFLAGS +htop_save_LIBS=$LIBS +CPPFLAGS="$AM_CPPFLAGS $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 +/* 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 +/* 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], @@ -1600,7 +1858,8 @@ AC_MSG_RESULT([ (Linux) capabilities: $enable_capabilities unicode: $enable_unicode affinity: $enable_affinity - unwind: $enable_unwind + backtrace: $enable_backtrace + demangling: $enable_demangling hwloc: $enable_hwloc debug: $enable_debug static: $enable_static diff --git a/generic/Demangle.c b/generic/Demangle.c new file mode 100644 index 000000000..3ce75f4fd --- /dev/null +++ b/generic/Demangle.c @@ -0,0 +1,65 @@ +/* +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 "config.h" // IWYU pragma: keep + +#include "generic/Demangle.h" + +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#ifdef HAVE_DEMANGLE_H +# if !defined(HAVE_DECL_BASENAME) +// Suppress libiberty's own declaration of basename(). +// 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() API. (The POSIX version is flawed by +// design; libiberty's ships with GNU version of basename() that's +// incompatible with POSIX.) +// +# define HAVE_DECL_BASENAME 1 +# endif +#include +#endif + + +#ifdef HAVE_DEMANGLING +char* Generic_Demangle(const char* mangled) { +# if defined(HAVE_LIBIBERTY_CPLUS_DEMANGLE) + int options = DMGL_PARAMS | DMGL_AUTO; + 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. + static size_t allocSize = 8; + + char* buf; + + while (!!(buf = malloc(allocSize))) { + int ret = cplus_demangle(mangled, buf, allocSize); + if (ret == 0) + break; + + free(buf); + buf = NULL; + + if (ret != DEMANGLE_ESPACE || allocSize > SIZE_MAX / 2) + break; + + allocSize *= 2; + } + + return buf; +# endif + + // Unimplemented + (void)mangled; + return NULL; +} +#endif diff --git a/generic/Demangle.h b/generic/Demangle.h new file mode 100644 index 000000000..ba1b97c22 --- /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* Generic_Demangle(const char* mangled); +#else +ATTR_NONNULL +static inline char* Generic_Demangle(const char* mangled) { + (void)mangled; + return NULL; +} +#endif + +#endif diff --git a/generic/UnwindPtrace.c b/generic/UnwindPtrace.c new file mode 100644 index 000000000..592376109 --- /dev/null +++ b/generic/UnwindPtrace.c @@ -0,0 +1,149 @@ +/* +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 "BacktraceScreen.h" +#include "XUtils.h" +#include "generic/Demangle.h" + +#ifdef HAVE_LIBUNWIND_PTRACE +#include +#include +#endif + + +#ifdef HAVE_LIBUNWIND_PTRACE +static int ptraceAttach(pid_t pid) { + errno = 0; +# 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; +# endif + (void)pid; + return ENOSYS; +} + +static int ptraceDetach(pid_t pid) { + errno = 0; +# 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; +# endif + (void)pid; + return ENOSYS; +} + +void UnwindPtrace_makeBacktrace(Vector* frames, pid_t pid, char** error) { + *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; + } + + 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) { + 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_DEMANGLING) + char* demangledName = Generic_Demangle(frame->functionName); + 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: + 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 8921dedab..60650b292 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 the compile time.) +.TP .B Ctrl-L Refresh: redraw screen and recalculate values. .TP