From cfe530041b9dc6b8e6ec5a8d1d3e87637b08f38b Mon Sep 17 00:00:00 2001 From: Odric Roux-Paris Date: Sat, 16 Mar 2024 18:24:44 +0100 Subject: [PATCH] Add backtrace screen --- Action.c | 29 +++++++ BacktraceScreen.c | 208 ++++++++++++++++++++++++++++++++++++++++++++++ BacktraceScreen.h | 46 ++++++++++ Makefile.am | 2 + Object.h | 1 + README | 6 ++ 6 files changed, 292 insertions(+) create mode 100644 BacktraceScreen.c create mode 100644 BacktraceScreen.h diff --git a/Action.c b/Action.c index 4049a952f..7b3056869 100644 --- a/Action.c +++ b/Action.c @@ -15,6 +15,7 @@ in the source distribution for its full text. #include #include +#include "BacktraceScreen.h" #include "CRT.h" #include "CategoriesPanel.h" #include "CommandScreen.h" @@ -595,6 +596,27 @@ static Htop_Reaction actionShowLocks(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } +#if (defined(HTOP_LINUX) && defined(HAVE_LIBUNWIND_PTRACE)) +static Htop_Reaction actionBacktrace(State *st) { + const Process* process = (Process*) Panel_getSelected((Panel*)st->mainPanel); + if (!process) + return HTOP_OK; + + BacktracePanel *panel = BacktracePanel_new(process); + ScreenManager *screenManager = ScreenManager_new(NULL, st->host, st, false); + ScreenManager_add(screenManager, (Panel *)panel, 0); + + Panel *lastFocusPanel = NULL; + int lastKey = 0; + + ScreenManager_run(screenManager, &lastFocusPanel, &lastKey, 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; @@ -679,6 +701,10 @@ static const struct { #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) { .key = " a: ", .roInactive = true, .info = "set CPU affinity" }, #endif + +#if (defined(HTOP_LINUX) && defined(HAVE_LIBUNWIND_PTRACE)) + { .key = " b: ", .roInactive = false, .info = "show the backtrace of user process" }, +#endif { .key = " e: ", .roInactive = false, .info = "show process environment" }, { .key = " i: ", .roInactive = true, .info = "set IO priority" }, { .key = " l: ", .roInactive = true, .info = "list open files with lsof" }, @@ -918,6 +944,9 @@ void Action_setBindings(Htop_Action* keys) { keys['\\'] = actionIncFilter; keys[']'] = actionHigherPriority; keys['a'] = actionSetAffinity; +#if (defined(HTOP_LINUX) && defined(HAVE_LIBUNWIND_PTRACE)) + 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..832819499 --- /dev/null +++ b/BacktraceScreen.c @@ -0,0 +1,208 @@ +/* +htop - Action.c +(C) 2015 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "BacktraceScreen.h" + +#if (defined(HTOP_LINUX) && defined(HAVE_LIBUNWIND_PTRACE)) + +#include + +#include "CRT.h" +#include "Object.h" +#include "Panel.h" +#include "Process.h" +#include "RichString.h" +#include "XUtils.h" +#include "errno.h" + +#include +#include +#include + +#define MAX_FRAME 256 + +static const char* const BacktraceScreenFunctions[] = {"Done ", NULL}; + +static const char* const BacktraceScreenKeys[] = {"Esc"}; + +static const int BacktraceScreenEvents[] = {27}; + +static void Frame_display(const Object* super, RichString* out) { + const Frame* const frame = (const Frame*)super; + if (frame->isError) { + RichString_appendAscii(out, CRT_colors[DEFAULT_COLOR], frame->error); + return; + } + + char bufferNumberOfFrame[16] = {'\0'}; + int len = snprintf(bufferNumberOfFrame, sizeof(bufferNumberOfFrame), "#%-3d ", frame->index); + RichString_appendnAscii(out, CRT_colors[DYNAMIC_GREEN], bufferNumberOfFrame, len); + + char bufferAddress[32] = {'\0'}; + len = snprintf(bufferAddress, sizeof(bufferAddress), "0x%016zx ", frame->address); + RichString_appendnAscii(out, CRT_colors[DYNAMIC_BLUE], bufferAddress, len); + + RichString_appendAscii(out, CRT_colors[DEFAULT_COLOR], frame->functionName); + if (frame->isSignalFrame) { + RichString_appendAscii(out, CRT_colors[DYNAMIC_RED], " signal frame"); + } + + char bufferFrameOffset[16] = {'\0'}; + len = snprintf(bufferFrameOffset, sizeof(bufferFrameOffset), "+%zu", frame->offset); + RichString_appendAscii(out, CRT_colors[DYNAMIC_YELLOW], bufferFrameOffset); +} + +static void BacktracePanel_getFrames(BacktracePanel* this) { + Panel* super = (Panel*) this; + + unw_addr_space_t addrSpace = unw_create_addr_space(&_UPT_accessors, 0); + if (!addrSpace) { + xAsprintf(&this->error, "Unable to init libunwind."); + return; + } + + const pid_t pid = Process_getPid(this->process); + + if (pid == 0) { + xAsprintf(&this->error, "Unable to get the pid"); + goto addr_space_error; + } + + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + xAsprintf(&this->error, "ptrace: %s", strerror(errno)); + goto addr_space_error; + } + wait(NULL); + + struct UPT_info* context = _UPT_create(pid); + if (!context) { + xAsprintf(&this->error, "Unable to init backtrace panel."); + goto ptrace_error; + } + + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, addrSpace, context); + if (ret < 0) { + xAsprintf(&this->error, "libunwind cursor: ret=%d", ret); + goto context_error; + } + + int index = 0; + do { + char procName[256] = "?"; + unw_word_t offset; + unw_word_t pc; + + if (unw_get_proc_name(&cursor, procName, sizeof(procName), &offset) == 0) { + ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (ret < 0) { + xAsprintf(&this->error, "unable to get register rip : %d", ret); + break; + } + + Frame* frame = Frame_new(); + frame->index = index; + frame->address = pc; + frame->offset = offset; + frame->isSignalFrame = unw_is_signal_frame(&cursor); +#if HAVE_LIBIBERTY + char* demangledName = cplus_demangle(procName, + DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE | DMGL_RET_POSTFIX); + if (demangledName == NULL) { + xAsprintf(&frame->functionName, "%s", procName); + } else { + xAsprintf(&frame->functionName, "%s", demangledName); + free(demangledName); + } +#else + xAsprintf(&frame->functionName, "%s", procName); +#endif + Panel_add(super, (Object*)frame); + } + index++; + } while (unw_step(&cursor) > 0 && index < MAX_FRAME); + +context_error: + _UPT_destroy(context); + +ptrace_error: + ptrace(PTRACE_DETACH, pid, 0, 0); + +addr_space_error: + unw_destroy_addr_space(addrSpace); +} + +BacktracePanel* BacktracePanel_new(const Process* process) { + BacktracePanel* this = CallocThis(BacktracePanel); + this->process = process; + + Panel* super = (Panel*) this; + Panel_init(super, 1, 1, 1, 1, Class(Frame), true, FunctionBar_new(BacktraceScreenFunctions, BacktraceScreenKeys, BacktraceScreenEvents)); + BacktracePanel_getFrames(this); + if (this->error) { + Panel_prune(super); + + Frame* errorFrame = Frame_new(); + errorFrame->error = xStrdup(this->error); + errorFrame->isError = true; + Panel_add(super, (Object*)errorFrame); + } + + char* header = NULL; + xAsprintf(&header, "Backtrace of '%s' (%d)", process->procComm, Process_getPid(process)); + Panel_setHeader(super, header); + free(header); + return this; +} + +Frame* Frame_new(void) { + Frame* this = CallocThis(Frame); + return this; +} + +static int Frame_compare(const void* object1, const void* object2) { + const Frame* frame1 = (const Frame*)object1; + const Frame* frame2 = (const Frame*)object2; + return String_eq(frame1->functionName, frame2->functionName); +} + +static void Frame_delete(Object* object) { + Frame* this = (Frame*)object; + if (this->functionName) { + free(this->functionName); + } + + if (this->isError && this->error) { + free(this->error); + } + + free(this); +} + +void BacktracePanel_delete(Object* object) { + BacktracePanel* this = (BacktracePanel*)object; + if (this->error) { + free(this->error); + } + Panel_delete(object); +} + +const PanelClass BacktracePanel_class = { + .super = { + .extends = Class(Panel), + .delete = BacktracePanel_delete, + }, +}; + +const ObjectClass Frame_class = { + .extends = Class(Object), + .compare = Frame_compare, + .delete = Frame_delete, + .display = Frame_display, +}; + +#endif /* HTOP_LINUX && HAVE_LIBUNWIND_PTRACE */ diff --git a/BacktraceScreen.h b/BacktraceScreen.h new file mode 100644 index 000000000..6e853dd1d --- /dev/null +++ b/BacktraceScreen.h @@ -0,0 +1,46 @@ +#ifndef HEADER_BacktraceScreen +#define HEADER_BacktraceScreen +/* +htop - Filename.h +(C) 2021 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 + +#if (defined(HTOP_LINUX) && defined(HAVE_LIBUNWIND_PTRACE)) + +#include + +#include "Panel.h" +#include "Process.h" + +typedef struct BacktracePanel_ { + Panel super; + const Process* process; + char* error; +} BacktracePanel; + +typedef struct Frame_ { + Object super; + int index; + size_t address; + size_t offset; + char* functionName; + bool isSignalFrame; + + bool isError; + char* error; +} Frame; + +BacktracePanel* BacktracePanel_new(const Process* process); +void BacktracePanel_delete(Object* object); +Frame* Frame_new(void); + +extern const PanelClass BacktracePanel_class; +extern const ObjectClass Frame_class; + +#endif /* HTOP_LINUX && HAVE_LIBUNWIND_PTRACE */ + +#endif diff --git a/Makefile.am b/Makefile.am index ed92afac4..df313f0e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ myhtopsources = \ AffinityPanel.c \ AvailableColumnsPanel.c \ AvailableMetersPanel.c \ + BacktraceScreen.c \ BatteryMeter.c \ CategoriesPanel.c \ ClockMeter.c \ @@ -99,6 +100,7 @@ myhtopheaders = \ AffinityPanel.h \ AvailableColumnsPanel.h \ AvailableMetersPanel.h \ + BacktraceScreen.h \ BatteryMeter.h \ CPUMeter.h \ CRT.h \ diff --git a/Object.h b/Object.h index 4d7d653ee..fa6797af5 100644 --- a/Object.h +++ b/Object.h @@ -33,6 +33,7 @@ typedef void(*Object_Delete)(Object*); #define Class(class_) ((const ObjectClass*)(&(class_ ## _class))) #define AllocThis(class_) (class_*) xMalloc(sizeof(class_)); Object_setClass(this, Class(class_)) +#define CallocThis(class_) (class_*) xCalloc(sizeof(class_), 1); Object_setClass(this, Class(class_)) typedef struct ObjectClass_ { const void* const extends; diff --git a/README b/README index 7ace7cd18..2e19f9e70 100644 --- a/README +++ b/README @@ -139,6 +139,12 @@ To install on the local system run `make install`. By default `make install` ins enable Linux delay accounting support - dependencies: *pkg-config*(build-time), *libnl-3* and *libnl-genl-3* - default: *check* + * `--enable-unwind-ptrace`: + enable backtrace support + - default: *no* + * `--enable-libiberty`: + enable the demangling support for the backtraces + - default: *no* ## Runtime dependencies: